diff --git a/Build.build.ps1 b/Build.build.ps1 index 2ecfbdf..c43268f 100644 --- a/Build.build.ps1 +++ b/Build.build.ps1 @@ -19,6 +19,15 @@ param( # Collect code coverage when tests are run [switch]$CollectCoverage, + # The PesterFilter from New-PesterConfiguration. + # Supports specifying any of: + # Tag: Tags of Describe, Context or It to be run. + # ExcludeTag: Tags of Describe, Context or It to be excluded from the run. + # Line: Filter by file and scriptblock start line, useful to run parsed tests programmatically to avoid problems with expanded names. Example: 'C:\tests\file1.Tests.ps1:37' + # ExcludeLine: Exclude by file and scriptblock start line, takes precedence over Line. + # FullName: Full name of test with -like wildcards, joined by dot. Example: '*.describe Get-Item.test1' + [hashtable]$PesterFilter, + # Which projects to build [Alias("Projects")] $dotnetProjects = @(), @@ -39,6 +48,7 @@ $ErrorView = 'DetailedView' # The name of the module to publish $script:PSModuleName = "TerminalBlocks" +$script:RequiredCodeCoverage = 0.85 # Use Env because Earthly can override it $Env:OUTPUT_ROOT ??= Join-Path $BuildRoot Modules diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 109c593..48b2068 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -2,7 +2,7 @@ @{ Configuration = "[1.5.0, 2.0)" Metadata = "[1.5.1, 2.0)" - Pester = "[4.10.1,5.0)" + Pester = "[5.6.1, 6.0)" ModuleBuilder = "[3.0.0, 4.0)" PSScriptAnalyzer = "[1.21.0,2.0)" PowerShellGet = "2.0.4" diff --git a/Source/Private/CompressToBase64.ps1 b/Source/Private/CompressToBase64.ps1 new file mode 100644 index 0000000..ff02d83 --- /dev/null +++ b/Source/Private/CompressToBase64.ps1 @@ -0,0 +1,57 @@ +function CompressToBase64 { + <# + .SYNOPSIS + Compresses and encodes a module file for embedding into a script + .DESCRIPTION + Reads the raw bytes and then compress (gzip) them, before base64 encoding the result + .EXAMPLE + Get-ChildItem *.dll, *.psm1 | CompressToBase64 -ExpandScript ImportBase64Module > Script.ps1 + + Script.ps1 will contain the base64 encoded (and compressed) contents of the files, piped to the ImportBase64Module function + .LINK + ImportBase64Module + #> + [CmdletBinding(DefaultParameterSetName = "Base64")] + [OutputType([string])] + param( + # The path to the dll or script file to compress + [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] + [Alias("PSPath")] + [string[]]$Path, + + # If set, wraps the Base64 encoded content in the specified command + [Parameter(Mandatory, Position = 1, ParameterSetName = "ExpandScriptName")] + [string]$ExpandScriptName, + + # If set, wraps the Base64 encoded content in the specified command + [Parameter(Mandatory, Position = 1, ParameterSetName = "ExpandScript")] + [ScriptBlock]$ExpandScript + ) + begin { + $Result = @() + if ($ExpandScriptName -and !$ExpandScript) { + $ExpandScript = (Get-Command $ExpandScriptName).ScriptBlock + } + } + process { + foreach ($File in $Path | Convert-Path) { + $Source = [System.IO.MemoryStream][System.IO.File]::ReadAllBytes($File) + $OutputStream = [System.IO.Compression.DeflateStream]::new( + [System.IO.MemoryStream]::new(), + [System.IO.Compression.CompressionMode]::Compress) + $Source.CopyTo($OutputStream) + $OutputStream.Flush() + $ByteArray = $OutputStream.BaseStream.ToArray() + if (!$ExpandScript) { + [Convert]::ToBase64String($ByteArray) + } else { + $Result += [Convert]::ToBase64String($ByteArray) + } + } + } + end { + if ($ExpandScript) { + [ScriptBlock]::Create("@(`n'$($Result -join "'`n'")'`n)|.{`n${ExpandScript}`n}").ToString() + } + } +} diff --git a/Source/Private/CopyReadMe.ps1 b/Source/Private/CopyReadMe.ps1 index 533cc5a..a8e8576 100644 --- a/Source/Private/CopyReadMe.ps1 +++ b/Source/Private/CopyReadMe.ps1 @@ -1,4 +1,8 @@ function CopyReadMe { + <# + .SYNOPSIS + Copy the readme file as an about_ help file + #> [CmdletBinding()] param( # The path to the ReadMe document to copy @@ -22,19 +26,18 @@ function CopyReadMe { [Switch]$Force ) process { - # Copy the readme file as an about_ help file - Write-Verbose "Test for ReadMe: $Pwd/$($ReadMe)" + Write-Verbose "Test for ReadMe: $ReadMe" if ($ReadMe -and (Test-Path $ReadMe -PathType Leaf)) { # Make sure there's a language path $LanguagePath = Join-Path $OutputDirectory $Culture if (!(Test-Path $LanguagePath -PathType Container)) { + Write-Verbose "Create language path: $LanguagePath" $null = New-Item $LanguagePath -Type Directory -Force } - Write-Verbose "Copy ReadMe to: $LanguagePath" $about_module = Join-Path $LanguagePath "about_$($ModuleName).help.txt" if (!(Test-Path $about_module)) { - Write-Verbose "Turn readme into about_module" + Write-Verbose "Copy $ReadMe to: $about_module" Copy-Item -LiteralPath $ReadMe -Destination $about_module -Force:$Force } } diff --git a/Source/Private/ImportBase64Module.ps1 b/Source/Private/ImportBase64Module.ps1 new file mode 100644 index 0000000..8141f42 --- /dev/null +++ b/Source/Private/ImportBase64Module.ps1 @@ -0,0 +1,43 @@ +# The comment-based help for this function is outside the function to avoid packing it +# Normally, I put it inside, but I do not want this in packed scripts + +<# + .SYNOPSIS + Expands Base64+GZip strings and loads the result as an assembly or module + .DESCRIPTION + Converts Base64 encoded string to bytes and decompresses (gzip) it. + If the result is a valid assembly, it is loaded. + Otherwise, it is imported as a module. + .PARAMETER Base64Content + A Base64 encoded and deflated assembly or script + .LINK + CompressToBase64 +#> +function ImportBase64Module { + [CmdletBinding(DefaultParameterSetName = "ByteArray")] + param( + [Parameter(Mandatory, ValueFromPipeline)] + [string]$Base64Content + ) + process { + $Out = [System.IO.MemoryStream]::new() + $In = [System.IO.MemoryStream][System.Convert]::FromBase64String($Base64Content) + $zip = [System.IO.Compression.DeflateStream]::new($In, [System.IO.Compression.CompressionMode]::Decompress) + $zip.CopyTo($Out) + trap [System.IO.InvalidDataException] { + Write-Debug "Base64Content not Compressed. Skipping Deflate." + $In.CopyTo($Out) + continue + } + $null = $Out.Seek(0, "Begin") + $null = [System.Reflection.Assembly]::Load($Out.ToArray()) + trap [BadImageFormatException] { + Write-Debug "Base64Content not an Assembly. Trying New-Module and ScriptBlock.Create." + $null = $Out.Seek(0, "Begin") + # Use StreamReader to handle possible BOM + $Source = [System.IO.StreamReader]::new($Out, $true).ReadToEnd() + $null = New-Module ([ScriptBlock]::Create($Source)) -Verbose:$false | Import-Module -Scope Global -Verbose:$false + continue + } + } +} diff --git a/Source/Private/MoveUsingStatements.ps1 b/Source/Private/MoveUsingStatements.ps1 deleted file mode 100644 index 82c2c87..0000000 --- a/Source/Private/MoveUsingStatements.ps1 +++ /dev/null @@ -1,77 +0,0 @@ -function MoveUsingStatements { - <# - .SYNOPSIS - A command to comment out and copy to the top of the file the Using Statements - .DESCRIPTION - When all files are merged together, the Using statements from individual files - don't necessarily end up at the beginning of the PSM1, creating Parsing Errors. - - This function uses AST to comment out those statements (to preserver line numbering) - and insert them (conserving order) at the top of the script. - #> - [CmdletBinding()] - param( - # Path to the PSM1 file to amend - [Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline)] - [System.Management.Automation.Language.Ast]$AST, - - [Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)] - [AllowNull()] - [System.Management.Automation.Language.ParseError[]]$ParseErrors, - - # The encoding defaults to UTF8 (or UTF8NoBom on Core) - [Parameter(DontShow)] - [string]$Encoding = $(if ($IsCoreCLR) { - "UTF8NoBom" - } else { - "UTF8" - }) - ) - process { - # Avoid modifying the file if there's no Parsing Error caused by Using Statements or other errors - if (!$ParseErrors.Where{ $_.ErrorId -eq 'UsingMustBeAtStartOfScript' }) { - Write-Debug "No using statement errors found." - return - } else { - # as decided https://github.com/PoshCode/ModuleBuilder/issues/96 - Write-Debug "Parsing errors found. We'll still attempt to Move using statements." - } - - # Find all Using statements including those non erroring (to conserve their order) - $UsingStatementExtents = $AST.FindAll( - { $Args[0] -is [System.Management.Automation.Language.UsingStatementAst] }, - $false - ).Extent - - # Edit the Script content by commenting out existing statements (conserving line numbering) - $ScriptText = $AST.Extent.Text - $InsertedCharOffset = 0 - $StatementsToCopy = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) - - foreach ($UsingSatement in $UsingStatementExtents) { - $ScriptText = $ScriptText.Insert($UsingSatement.StartOffset + $InsertedCharOffset, '#') - $InsertedCharOffset++ - - # Keep track of unique statements we'll need to insert at the top - $null = $StatementsToCopy.Add($UsingSatement.Text) - } - - $ScriptText = $ScriptText.Insert(0, ($StatementsToCopy -join "`r`n") + "`r`n") - $null = Set-Content -Value $ScriptText -Path $RootModule -Encoding $Encoding - - # Verify we haven't introduced new Parsing errors - $null = [System.Management.Automation.Language.Parser]::ParseFile( - $RootModule, - [ref]$null, - [ref]$ParseErrors - ) - - if ($ParseErrors.Count) { - $Message = $ParseErrors | - Format-Table -Auto @{n = "File"; expr = { $_.Extent.File | Split-Path -Leaf }}, - @{n = "Line"; expr = { $_.Extent.StartLineNumber }}, - Extent, ErrorId, Message | Out-String - Write-Warning "Parse errors in build output:`n$Message" - } - } -} diff --git a/Source/Public/Move-UsingStatement.ps1 b/Source/Public/Move-UsingStatement.ps1 new file mode 100644 index 0000000..4710361 --- /dev/null +++ b/Source/Public/Move-UsingStatement.ps1 @@ -0,0 +1,65 @@ +using namespace System.Management.Automation.Language + +function Move-UsingStatement { + <# + .SYNOPSIS + A Script Generator that commetnts out using statements and copies them to the top of the file + .DESCRIPTION + Move-UsingStatement supports having using statements repeated in multiple files that are merged by ModuleBuilder. + When all the files are merged together, the using statements from individual files + don't necessarily end up at the beginning of the PSM1, which creates Parsing Errors. + + This function uses the AST to generate TextReplacements to: + 1. Comment out the original using statements (to preserve line numbering) + 2. Insert the using statements (conserving order, but removing duplicates) at the top of the script + #> + [CmdletBinding()] + [OutputType([TextReplacement])] + param( + # The AST of the original script module to refactor + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [Alias("Ast")] + [Ast]$InputObject, + + # Parser Errors from parsing the original script module + [Parameter(ValueFromPipelineByPropertyName)] + [AllowNull()] + [ParseError[]]$ParseErrors + ) + process { + # Avoid modifying the file if there's no Parsing Error caused by Using Statements or other errors + if (!$ParseErrors.Where{ $_.ErrorId -eq 'UsingMustBeAtStartOfScript' }) { + Write-Debug "No using statement errors found." + return + } else { + # as decided https://github.com/PoshCode/ModuleBuilder/issues/96 + Write-Debug "Parsing errors found. We'll still attempt to Move using statements." + } + + # Find all Using statements including those non erroring (to conserve their order) + $UsingStatementExtents = $InputObject.FindAll( + { $Args[0] -is [System.Management.Automation.Language.UsingStatementAst] }, + $false + ).Extent + + # Edit the Script content by commenting out existing statements (conserving line numbering) + $StatementsToCopy = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) + + foreach ($UsingSatement in $UsingStatementExtents) { + [TextReplacement]@{ + StartOffset = $UsingSatement.StartOffset + EndOffset = $UsingSatement.EndOffset + Text = '# ' + $UsingSatement.Text + } + # Keep track of unique statements we'll need to insert at the top + $null = $StatementsToCopy.Add($UsingSatement.Text) + } + if ($StatementsToCopy.Count -gt 0) { + [TextReplacement]@{ + StartOffset = 0 + EndOffset = 0 + Text = ($StatementsToCopy -join "`r`n")+ "`r`n" + } + } + } +} diff --git a/Tests/Integration/Parameters.Tests.ps1 b/Tests/Integration/Parameters.Tests.ps1 index 6206696..dd0b7d6 100644 --- a/Tests/Integration/Parameters.Tests.ps1 +++ b/Tests/Integration/Parameters.Tests.ps1 @@ -1,9 +1,11 @@ -#requires -Module ModuleBuilder . $PSScriptRoot\..\Convert-FolderSeparator.ps1 Describe "Parameters.Set in build manifest" -Tag Integration { BeforeAll { + # This file should not be deleted by the build, because VersionedOutput defaults true now + # So the actual Build output will be ../Result3/Parameters/3.0.0 New-Item $PSScriptRoot\Result3\Parameters\ReadMe.md -ItemType File -Force + $Output = Build-Module $PSScriptRoot\Parameters\build.psd1 if ($Output) { $Module = [IO.Path]::ChangeExtension($Output.Path, "psm1") @@ -11,19 +13,19 @@ Describe "Parameters.Set in build manifest" -Tag Integration { } } - It "Passthru works" { + It "Passthru can be set from build.psd1" { $Output | Should -Not -BeNullOrEmpty } - It "The Target is Build" { - "$PSScriptRoot\Result3\Parameters\ReadMe.md" | Should -Exist + It "The version can be set from build.psd1" { + $Metadata.ModuleVersion | Should -Be "3.0.0" } - It "The version is set" { - $Metadata.ModuleVersion | Should -Be "3.0.0" + It "Files outside the Output should not be cleaned up or overwritten" { + "$PSScriptRoot\Result3\Parameters\ReadMe.md" | Should -Exist } - It "The PreRelease is set" { + It "The PreRelease is set properly even when set from build.psd1" { $Metadata.PrivateData.PSData.Prerelease | Should -Be 'alpha001' } } diff --git a/Tests/Integration/Source1.Tests.ps1 b/Tests/Integration/Source1.Tests.ps1 index 63b5bb9..633737d 100644 --- a/Tests/Integration/Source1.Tests.ps1 +++ b/Tests/Integration/Source1.Tests.ps1 @@ -1,16 +1,17 @@ -#requires -Module ModuleBuilder . $PSScriptRoot\..\Convert-FolderSeparator.ps1 +$ProgressPreference = "SilentlyContinue" Describe "When we call Build-Module" -Tag Integration { - $Output = Build-Module $PSScriptRoot\Source1\build.psd1 -Passthru - $Module = [IO.Path]::ChangeExtension($Output.Path, "psm1") + BeforeAll { + $Output = Build-Module $PSScriptRoot\Source1\build.psd1 -Passthru + $Metadata = Import-Metadata $Output.Path + $Module = [IO.Path]::ChangeExtension($Output.Path, "psm1") + } It "Should not put the module's DefaultCommandPrefix into the psm1 as code. Duh!" { $Module | Should -Not -FileContentMatch '^Source$' } - $Metadata = Import-Metadata $Output.Path - It "Should update FunctionsToExport in the manifest" { $Metadata.FunctionsToExport | Should -Be @("Get-Source", "Set-Source") } @@ -29,15 +30,16 @@ Describe "When we call Build-Module" -Tag Integration { } Describe "Regression test for #55: I can pass SourceDirectories" -Tag Integration, Regression { - $Output = Build-Module $PSScriptRoot\Source1\build.psd1 -SourceDirectories "Private" -Passthru - $Module = [IO.Path]::ChangeExtension($Output.Path, "psm1") + BeforeAll { + $Output = Build-Module $PSScriptRoot\Source1\build.psd1 -SourceDirectories "Private" -Passthru + $Metadata = Import-Metadata $Output.Path + $Module = [IO.Path]::ChangeExtension($Output.Path, "psm1") + } It "Should not put the module's DefaultCommandPrefix into the psm1 as code. Duh!" { $Module | Should -Not -FileContentMatch '^Source$' } - $Metadata = Import-Metadata $Output.Path - It "Should not have any FunctionsToExport if SourceDirectories don't match the PublicFilter" { $Metadata.FunctionsToExport | Should -Be @() } @@ -54,8 +56,8 @@ Describe "Regression test for #55: I can pass SourceDirectories" -Tag Integratio Describe "Regression test for #55: I can pass SourceDirectories and PublicFilter" -Tag Integration, Regression { BeforeAll { $Output = Build-Module $PSScriptRoot\Source1\build.psd1 -SourceDirectories "Private" -PublicFilter "Pub*\*" -Passthru - $Module = [IO.Path]::ChangeExtension($Output.Path, "psm1") $Metadata = Import-Metadata $Output.Path + $Module = [IO.Path]::ChangeExtension($Output.Path, "psm1") } It "Should not put the module's DefaultCommandPrefix into the psm1 as code. Duh!" { @@ -80,9 +82,10 @@ Describe "Regression test for #55: I can pass SourceDirectories and PublicFilter } Describe "Regression test for #84: Multiple Aliases per command will Export" -Tag Integration, Regression { - $Output = Build-Module $PSScriptRoot\Source1\build.psd1 -Passthru - - $Metadata = Import-Metadata $Output.Path + BeforeAll { + $Output = Build-Module $PSScriptRoot\Source1\build.psd1 -Passthru + $Metadata = Import-Metadata $Output.Path + } It "Should update AliasesToExport in the manifest" { $Metadata.AliasesToExport | Should -Be @("Get-MyAlias","GS","GSou", "SS", "SSou") @@ -90,17 +93,19 @@ Describe "Regression test for #84: Multiple Aliases per command will Export" -Ta } Describe "Supports building without a build.psd1" -Tag Integration { - Copy-Item $PSScriptRoot\Source1 TestDrive:\Source1 -Recurse - # This is the old build, with a build.psd1 - $Output = Build-Module TestDrive:\Source1\build.psd1 -Passthru - $ManifestContent = Get-Content $Output.Path - $ModuleContent = Get-Content ([IO.Path]::ChangeExtension($Output.Path, ".psm1")) - Remove-Item (Split-Path $Output.Path) -Recurse + BeforeAll { + Copy-Item $PSScriptRoot\Source1 TestDrive:\Source1 -Recurse + # This is the old build, with a build.psd1 + $Output = Build-Module TestDrive:\Source1\build.psd1 -Passthru + $ManifestContent = Get-Content $Output.Path + $ModuleContent = Get-Content ([IO.Path]::ChangeExtension($Output.Path, ".psm1")) + Remove-Item (Split-Path $Output.Path) -Recurse - # Then remove the build.psd1 and rebuild it - Remove-Item TestDrive:\Source1\build.psd1 + # Then remove the build.psd1 and rebuild it + Remove-Item TestDrive:\Source1\build.psd1 - $Build = @{ } + $Build = @{ } + } It "No longer fails if there's no build.psd1" { $BuildParameters = @{ @@ -158,17 +163,19 @@ Describe "Supports building without a build.psd1" -Tag Integration { } Describe "Defaults to VersionedOutputDirectory" -Tag Integration { - Copy-Item $PSScriptRoot\Source1 TestDrive:\Source1 -Recurse - # This is the old build, with a build.psd1 - $Output = Build-Module TestDrive:\Source1\build.psd1 -Passthru - $ManifestContent = Get-Content $Output.Path - $ModuleContent = Get-Content ([IO.Path]::ChangeExtension($Output.Path, ".psm1")) - Remove-Item (Split-Path $Output.Path) -Recurse + BeforeAll { + Copy-Item $PSScriptRoot\Source1 TestDrive:\Source1 -Recurse + # This is the old build, with a build.psd1 + $Output = Build-Module TestDrive:\Source1\build.psd1 -Passthru + $ManifestContent = Get-Content $Output.Path + $ModuleContent = Get-Content ([IO.Path]::ChangeExtension($Output.Path, ".psm1")) + Remove-Item (Split-Path $Output.Path) -Recurse - # Then remove the build.psd1 and rebuild it - Remove-Item TestDrive:\Source1\build.psd1 + # Then remove the build.psd1 and rebuild it + Remove-Item TestDrive:\Source1\build.psd1 - $Build = @{ } + $Build = @{ } + } It "Builds into a folder with version by default" { $BuildParameters = @{ @@ -207,20 +214,22 @@ Describe "Defaults to VersionedOutputDirectory" -Tag Integration { } Describe "Supports building discovering the module without a build.psd1" -Tag Integration { - Copy-Item $PSScriptRoot\Source1 TestDrive:\source -Recurse + BeforeAll { + Copy-Item $PSScriptRoot\Source1 TestDrive:\source -Recurse - # This is the old build, with a build.psd1 - $Output = Build-Module TestDrive:\source\build.psd1 -Passthru - $ManifestContent = Get-Content $Output.Path - $ModuleContent = Get-Content ([IO.Path]::ChangeExtension($Output.Path, ".psm1")) - Remove-Item (Split-Path $Output.Path) -Recurse + # This is the old build, with a build.psd1 + $Output = Build-Module TestDrive:\source\build.psd1 -Passthru + $ManifestContent = Get-Content $Output.Path + $ModuleContent = Get-Content ([IO.Path]::ChangeExtension($Output.Path, ".psm1")) + Remove-Item (Split-Path $Output.Path) -Recurse - # Then remove the build.psd1 and rebuild it - Remove-Item TestDrive:\source\build.psd1 + # Then remove the build.psd1 and rebuild it + Remove-Item TestDrive:\source\build.psd1 - Push-Location -StackName 'IntegrationTest' -Path TestDrive:\ + Push-Location -StackName 'IntegrationTest' -Path TestDrive:\ - $Build = @{ } + $Build = @{ } + } It "No longer fails if there's no build.psd1" { $Build.Output = Build-Module -Passthru @@ -239,16 +248,18 @@ Describe "Supports building discovering the module without a build.psd1" -Tag In It "Should update FunctionsToExport in the manifest" { $Build.Metadata.FunctionsToExport | Should -Be @("Get-Source", "Set-Source") } - - Pop-Location -StackName 'IntegrationTest' + AfterAll { + Pop-Location -StackName 'IntegrationTest' + } } Describe "Regression test for #88 not copying prefix files" -Tag Integration, Regression { - $Output = Build-Module $PSScriptRoot\build.psd1 -Passthru - - $Metadata = Import-Metadata $Output.Path + BeforeAll { + $Output = Build-Module $PSScriptRoot\build.psd1 -Passthru + $Metadata = Import-Metadata $Output.Path + } - It "Should update AliasesToExport in the manifest" { + It "Should update the top of the module with that prefix" { $Module = [IO.Path]::ChangeExtension($Output.Path, "psm1") $ModuleInfo = Get-Content $Module $ModuleInfo[0] | Should -be "using module Configuration" @@ -256,21 +267,22 @@ Describe "Regression test for #88 not copying prefix files" -Tag Integration, Re } Describe "Regression test for #40.2 not copying suffix if prefix" -Tag Integration, Regression { - Copy-Item $PSScriptRoot\Source1 TestDrive:\Source1 -Recurse - - New-Item TestDrive:\Source1\_GlobalScope.ps1 -Value '$Global:Module = "Testing"' + BeforeAll { + Copy-Item $PSScriptRoot\Source1 TestDrive:\Source1 -Recurse - $metadata = Import-Metadata TestDrive:\Source1\build.psd1 - $metadata += @{ - Prefix = "./_GlobalScope.ps1" - Suffix = "./_GlobalScope.ps1" - } - $metadata | Export-Metadata TestDrive:\Source1\build.psd1 + New-Item TestDrive:\Source1\_GlobalScope.ps1 -Value '$Global:Module = "Testing"' - $Output = Build-Module TestDrive:\Source1 -Passthru + $metadata = Import-Metadata TestDrive:\Source1\build.psd1 + $metadata += @{ + Prefix = "./_GlobalScope.ps1" + Suffix = "./_GlobalScope.ps1" + } + $metadata | Export-Metadata TestDrive:\Source1\build.psd1 - $Metadata = Import-Metadata $Output.Path + $Output = Build-Module TestDrive:\Source1 -Passthru + $Metadata = Import-Metadata $Output.Path + } It "Should inject the content of the _GlobalScope file at the TOP and BOTTOM" { $Module = [IO.Path]::ChangeExtension($Output.Path, "psm1") $Code = Get-Content $Module @@ -288,10 +300,12 @@ Describe "Regression test for #40.2 not copying suffix if prefix" -Tag Integrati # There's no such thing as a drive root on unix if ($PSVersionTable.Platform -eq "Win32NT") { Describe "Able to build from the drive root" { - $null = New-ModuleManifest "TestDrive:/MyModule.psd1" -ModuleVersion "1.0.0" -Author "Tester" - $null = New-Item "TestDrive:/Public/Test.ps1" -Type File -Value 'MATCHING TEST CONTENT' -Force + BeforeAll { + $null = New-ModuleManifest "TestDrive:/MyModule.psd1" -ModuleVersion "1.0.0" -Author "Tester" + $null = New-Item "TestDrive:/Public/Test.ps1" -Type File -Value 'MATCHING TEST CONTENT' -Force - $Result = Build-Module -SourcePath 'TestDrive:/MyModule.psd1' -Version "1.0.0" -OutputDirectory './output' -Encoding UTF8 -SourceDirectories @('Public') -Target Build -Passthru + $Result = Build-Module -SourcePath 'TestDrive:/MyModule.psd1' -Version "1.0.0" -OutputDirectory './output' -Encoding UTF8 -SourceDirectories @('Public') -Target Build -Passthru + } It "Builds the Module in the designated output folder" { $Result.ModuleBase | Convert-FolderSeparator | Should -Be (Convert-FolderSeparator "TestDrive:/Output/MyModule/1.0.0") @@ -301,20 +315,21 @@ if ($PSVersionTable.Platform -eq "Win32NT") { } Describe "Copies additional items specified in CopyPaths" { + BeforeAll { + $null = New-Item "TestDrive:/build.psd1" -Type File -Force -Value "@{ + SourcePath = 'TestDrive:/MyModule.psd1' + SourceDirectories = @('Public') + OutputDirectory = './output' + CopyPaths = './lib', './MyModule.format.ps1xml' + }" + $null = New-ModuleManifest "TestDrive:/MyModule.psd1" -ModuleVersion "1.0.0" -Author "Tester" + $null = New-Item "TestDrive:/Public/Test.ps1" -Type File -Value 'MATCHING TEST CONTENT' -Force + $null = New-Item "TestDrive:/MyModule.format.ps1xml" -Type File -Value '' -Force + $null = New-Item "TestDrive:/lib/imaginary1.dll" -Type File -Value '1' -Force + $null = New-Item "TestDrive:/lib/subdir/imaginary2.dll" -Type File -Value '2' -Force - $null = New-Item "TestDrive:/build.psd1" -Type File -Force -Value "@{ - SourcePath = 'TestDrive:/MyModule.psd1' - SourceDirectories = @('Public') - OutputDirectory = './output' - CopyPaths = './lib', './MyModule.format.ps1xml' - }" - $null = New-ModuleManifest "TestDrive:/MyModule.psd1" -ModuleVersion "1.0.0" -Author "Tester" - $null = New-Item "TestDrive:/Public/Test.ps1" -Type File -Value 'MATCHING TEST CONTENT' -Force - $null = New-Item "TestDrive:/MyModule.format.ps1xml" -Type File -Value '' -Force - $null = New-Item "TestDrive:/lib/imaginary1.dll" -Type File -Value '1' -Force - $null = New-Item "TestDrive:/lib/subdir/imaginary2.dll" -Type File -Value '2' -Force - - $Result = Build-Module -SourcePath 'TestDrive:/build.psd1' -OutputDirectory './output' -Version '1.0.0' -Passthru -Target Build + $Result = Build-Module -SourcePath 'TestDrive:/build.psd1' -OutputDirectory './output' -Version '1.0.0' -Passthru -Target Build + } It "Copies single files that are in CopyPaths" { (Convert-FolderSeparator $Result.ModuleBase) | Should -Be (Convert-FolderSeparator "$TestDrive/output/MyModule/1.0.0") diff --git a/Tests/Integration/Source2/Generators/NewTerminalBlock.ps1 b/Tests/Integration/Source2/Generators/NewTerminalBlock.ps1 new file mode 100644 index 0000000..bb25eb5 --- /dev/null +++ b/Tests/Integration/Source2/Generators/NewTerminalBlock.ps1 @@ -0,0 +1,95 @@ +using module ModuleBuilder +class TerminalBlockGenerator : ScriptGenerator { + + [void] Generate() { + + $base.AddParameter({ + param( + [Alias("Prepend")] + [String]$Prefix, + + [Alias("Suffix", "Append")] + [String]$Postfix, + + # The separator character(s) are used between blocks of output by this scriptblock + # Pass two characters: the first for normal (Left aligned) blocks, the second for right-aligned blocks + [ArgumentCompleter({ + [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new( + [System.Management.Automation.CompletionResult[]]@( + # The Consolas-friendly block characters ▌and▐ and ╲ followed by all the extended Terminal characters + @([string[]][char[]]@(@(0xe0b0..0xe0d4) + @(0x2588..0x259b) + @(0x256d..0x2572))).ForEach({ + [System.Management.Automation.CompletionResult]::new("'$_'", $_, "ParameterValue", $_) }) + )) + })] + [String]$Separator, + + # The cap character(s) are used on the ends of blocks of output + # Pass two characters: the first for the left side, the second for the right side. + [ArgumentCompleter({ + [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new( + [System.Management.Automation.CompletionResult[]]@( + # The Consolas-friendly block characters ▌and▐ and ╲ followed by all the extended Terminal characters + @([string[]][char[]]@(@(0xe0b0..0xe0d4) + @(0x2588..0x259b) + @(0x256d..0x2572))).ForEach({ + [System.Management.Automation.CompletionResult]::new("'$_'", $_, "ParameterValue", $_) }) + )) + })] + [PoshCode.BlockCaps]$Caps, + + # The foreground color to use when the last command succeeded + [Alias("ForegroundColor", "Fg", "DFg")] + [AllowNull()][EmptyStringAsNull()] + [ArgumentCompleter([PoshCode.Pansies.Palettes.X11Palette])] + [PoshCode.Pansies.RgbColor]$DefaultForegroundColor, + + # The background color to use when the last command succeeded + [Alias("BackgroundColor", "Bg", "DBg")] + [AllowNull()][EmptyStringAsNull()] + [ArgumentCompleter([PoshCode.Pansies.Palettes.X11Palette])] + [PoshCode.Pansies.RgbColor]$DefaultBackgroundColor, + + # The foreground color to use when the process is elevated (running as administrator) + [Alias("AdminFg", "AFg")] + [AllowNull()][EmptyStringAsNull()] + [ArgumentCompleter([PoshCode.Pansies.Palettes.X11Palette])] + [PoshCode.Pansies.RgbColor]$AdminForegroundColor, + + # The background color to use when the process is elevated (running as administrator) + [Alias("AdminBg", "ABg")] + [AllowNull()][EmptyStringAsNull()] + [ArgumentCompleter([PoshCode.Pansies.Palettes.X11Palette])] + [PoshCode.Pansies.RgbColor]$AdminBackgroundColor, + + # The foreground color to use when the last command failed + [Alias("ErrorFg", "EFg")] + [AllowNull()][EmptyStringAsNull()] + [ArgumentCompleter([PoshCode.Pansies.Palettes.X11Palette])] + [PoshCode.Pansies.RgbColor]$ErrorForegroundColor, + + # The background color to use when the last command failed + [Alias("ErrorBg", "EBg")] + [AllowNull()][EmptyStringAsNull()] + [ArgumentCompleter([PoshCode.Pansies.Palettes.X11Palette])] + [PoshCode.Pansies.RgbColor]$ErrorBackgroundColor + ) + }) + + $base.AddBeforeEnd(@' + # Support default parameter values + $Parameters = Get-ParameterValue + $Parameters["Content"] = { +'@) + $base.AddAfterEnd(@' + }.GetNewClosure() + + # Strip common parameters if they're on here (so we can use -Verbose) + foreach ($name in @($Parameters.Keys.Where{ $_ -notin [PoshCode.TerminalBlock].GetProperties().Name })) { + $null = $Parameters.Remove($name) + } + + # Store the InvocationInfo for serialization + $Parameters["MyInvocation"] = [System.Management.Automation.InvocationInfo].GetProperty("ScriptPosition", [System.Reflection.BindingFlags]"Instance,NonPublic").GetValue($MyInvocation).Text + + [PoshCode.TerminalBlock]$Parameters +'@) + } +} diff --git a/Tests/Private/CompressToBase64.Tests.ps1 b/Tests/Private/CompressToBase64.Tests.ps1 new file mode 100644 index 0000000..e5c4268 --- /dev/null +++ b/Tests/Private/CompressToBase64.Tests.ps1 @@ -0,0 +1,61 @@ +Describe "CompressToBase64" { + Context "It compresses and encodes a file for embedding into a script" { + BeforeAll { + $Base64 = InModuleScope ModuleBuilder { + CompressToBase64 $PSCommandPath + } + } + + It "Returns a base64 encoded string" { + $Base64 | Should -BeOfType [string] + $Base64 | Should -Match "^[A-Za-z0-9\+\/]+=*$" + $Base64.Length | Should -BeGreaterThan 0 + } + + It "Returns the gzipped and encoded script" { + $OutputStream = [System.IO.MemoryStream]::new() + $InputStream = [System.IO.MemoryStream][System.Convert]::FromBase64String($Base64) + $DeflateStream = [System.IO.Compression.DeflateStream]::new($InputStream, [System.IO.Compression.CompressionMode]::Decompress) + $DeflateStream.CopyTo($OutputStream) + $OutputStream.Seek(0, "Begin") + $Source = [System.IO.StreamReader]::new($OutputStream, $true).ReadToEnd() + + $Source | Should -Be (Get-Content $PSCommandPath -Raw) + } + } + + Context "It wraps the Base64 encoded content in the specified command" { + BeforeAll { + $Base64 = InModuleScope ModuleBuilder { + CompressToBase64 $PSCommandPath -ExpandScriptName ImportBase64Module + } + } + + It "Returns a string" { + $Base64 | Should -BeOfType [string] + } + + It "Pipes the encoding into the command" { + $Block = InModuleScope ModuleBuilder { + (Get-Command ImportBase64Module).ScriptBlock + } + + $Base64 | Should -Match "|.{`n$Block`n}$" + } + } + + Context "It wraps the Base64 encoded content in the specified scriptblock" { + BeforeAll { + $Base64 = InModuleScope ModuleBuilder { + Get-ChildItem $PSCommandPath | CompressToBase64 -ExpandScript { ImportBase64Module } + } + } + + It "Returns a string" { + $Base64 | Should -BeOfType [string] + } + It "Pipes the encoding into the scriptblock" { + $Base64 | Should -Match "|.{`nImportBase64Module`n}$" + } + } +} diff --git a/Tests/Private/ConvertToAst.Tests.ps1 b/Tests/Private/ConvertToAst.Tests.ps1 index 5ccca7e..4d49920 100644 --- a/Tests/Private/ConvertToAst.Tests.ps1 +++ b/Tests/Private/ConvertToAst.Tests.ps1 @@ -1,9 +1,16 @@ -#requires -Module ModuleBuilder Describe "ConvertToAst" { + BeforeAll { + $PSDefaultParameterValues = @{ + "Mock:ModuleName" = "ModuleBuilder" + "Assert-MockCalled:ModuleName" = "ModuleBuilder" + } + } Context "It returns a ParseResult for file paths" { - $ParseResult = InModuleScope ModuleBuilder { - ConvertToAst $PSCommandPath + BeforeAll { + $ParseResult = InModuleScope ModuleBuilder { + ConvertToAst -Code $PSCommandPath + } } It "Returns a ParseResult object" { @@ -18,12 +25,13 @@ Describe "ConvertToAst" { It "Has a Tokens property" { $ParseResult.Tokens | Should -BeOfType [System.Management.Automation.Language.Token] } - } Context "It parses piped in commands" { - $ParseResult = InModuleScope ModuleBuilder { - Get-Command ConvertToAst | ConvertToAst + BeforeAll { + $ParseResult = InModuleScope ModuleBuilder { + Get-Command ConvertToAst | ConvertToAst + } } It "Returns a ParseResult object with the AST" { @@ -33,8 +41,10 @@ Describe "ConvertToAst" { } Context "It parses piped in modules" { - $ParseResult = InModuleScope ModuleBuilder { - Get-Module ModuleBuilder | ConvertToAst + BeforeAll { + $ParseResult = InModuleScope ModuleBuilder { + Get-Module ModuleBuilder | ConvertToAst + } } It "Returns a ParseResult object with the AST" { diff --git a/Tests/Private/CopyReadMe.Tests.ps1 b/Tests/Private/CopyReadMe.Tests.ps1 index b72bc71..04d0ac2 100644 --- a/Tests/Private/CopyReadMe.Tests.ps1 +++ b/Tests/Private/CopyReadMe.Tests.ps1 @@ -1,61 +1,60 @@ -#requires -Module ModuleBuilder Describe "Copy ReadMe" { - . $PSScriptRoot\..\Convert-FolderSeparator.ps1 + BeforeAll { + . $PSScriptRoot\..\Convert-FolderSeparator.ps1 + $PSDefaultParameterValues = @{ + "Mock:ModuleName" = "ModuleBuilder" + "Assert-MockCalled:ModuleName" = "ModuleBuilder" + } + } Context "There's no ReadMe" { # It should not even call Test-Path It "Does nothing if no ReadMe is passed" { - Mock Test-Path -ModuleName ModuleBuilder + Mock Test-Path InModuleScope ModuleBuilder { Get-Module ModuleBuilder | CopyReadMe -OutputDirectory TestDrive:\ } - Assert-MockCalled Test-Path -Times 0 -ModuleName ModuleBuilder + Assert-MockCalled Test-Path -Times 0 } # It's possible it should warn in this case? It "Does nothing if ReadMe doesn't exist" { - Mock Test-Path -ModuleName ModuleBuilder - Mock Join-Path -ModuleName ModuleBuilder + Mock Test-Path + Mock Join-Path InModuleScope ModuleBuilder { Get-Module ModuleBuilder | CopyReadMe -Readme ReadMe.md -OutputDirectory TestDrive:\ } # Test-Path is only called once -- means it didn't check for the folder - Assert-MockCalled Test-Path -Times 1 -ModuleName ModuleBuilder - Assert-MockCalled Join-Path -Times 0 -ModuleName ModuleBuilder + Assert-MockCalled Test-Path -Times 1 + Assert-MockCalled Join-Path -Times 0 } } Context "There is a ReadMe" { - # Nothing is actually created when this test runs - Mock New-Item -ModuleName ModuleBuilder - Mock Copy-Item -ModuleName ModuleBuilder - - # Test-Path returns true only for the source document - ${global:Test Script Path} = Join-Path $PSScriptRoot CopyReadMe.Tests.ps1 - Mock Test-Path { $Path -eq ${global:Test Script Path} } -ModuleName ModuleBuilder + BeforeAll { + # Test-Path returns true only for the source document + ${global:Test Script Path} = Join-Path $PSScriptRoot CopyReadMe.Tests.ps1 - Remove-Item TestDrive:\En -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item "$TestDrive/en" -Recurse -Force -ErrorAction SilentlyContinue - InModuleScope ModuleBuilder { - CopyReadMe -ReadMe ${global:Test Script Path} -Module ModuleBuilder -OutputDirectory TestDrive:\ -Culture "En" + InModuleScope ModuleBuilder { + CopyReadMe -ReadMe ${global:Test Script Path} -Module ModuleBuilder -OutputDirectory $TestDrive -Culture "en" + } } It "Creates a language path in the output" { - Assert-MockCalled New-Item -ModuleName ModuleBuilder -ParameterFilter { - (Convert-FolderSeparator "$Path") -eq (Convert-FolderSeparator "TestDrive:\En") - } + "$TestDrive/en" | Should -Exist } It "Copies the readme as about_module.help.txt" { - Assert-MockCalled Copy-Item -ModuleName ModuleBuilder -ParameterFilter { - (Convert-FolderSeparator $Destination) -eq (Convert-FolderSeparator "TestDrive:\En\about_ModuleBuilder.help.txt") - } + "$TestDrive/en/about_ModuleBuilder.help.txt" | Should -Exist + } + AfterAll { + Remove-Item "$TestDrive/en" -Recurse -Force -ErrorAction SilentlyContinue } - - Remove-Item TestDrive:\En -Recurse -Force -ErrorAction SilentlyContinue } } diff --git a/Tests/Private/GetBuildInfo.Tests.ps1 b/Tests/Private/GetBuildInfo.Tests.ps1 index ab0ba09..1dbea4f 100644 --- a/Tests/Private/GetBuildInfo.Tests.ps1 +++ b/Tests/Private/GetBuildInfo.Tests.ps1 @@ -1,79 +1,73 @@ -#requires -Module ModuleBuilder Describe "GetBuildInfo" { . $PSScriptRoot\..\Convert-FolderSeparator.ps1 - Mock Import-Metadata -ModuleName ModuleBuilder { - @{ - #Omitting path to let it resolve [Path = "MyModule.psd1"] - SourceDirectories = "Classes", "Public" + BeforeAll { + $PSDefaultParameterValues = @{ + "Mock:ModuleName" = "ModuleBuilder" + "Assert-MockCalled:ModuleName" = "ModuleBuilder" } } - Context "It collects the initial data" { - - # use -Force to create the subdirectories - New-Item -Force "TestDrive:\MyModule\Source\build.psd1" -Type File -Value "@{ Path = 'MyModule.psd1' }" - New-ModuleManifest "TestDrive:\MyModule\Source\MyModule.psd1" -Author Tester + Context "Collects the initial data" { + BeforeAll { + # use -Force to create the subdirectories + New-Item -Force "$TestDrive/MyModule/Source/build.psd1" -Type File -Value "@{ Path = 'MyModule.psd1'; SourceDirectories = 'Classes', 'Public' }" + New-ModuleManifest "$TestDrive/MyModule/Source/MyModule.psd1" -Author Tester - $Result = InModuleScope -ModuleName ModuleBuilder { + $Result = InModuleScope ModuleBuilder { - # Used to resolve the overridden parameters in $Invocation - $OutputDirectory = '..\ridiculoustestvalue' + # Used to resolve the overridden parameters in $Invocation + $OutputDirectory = '../ridiculoustestvalue' - GetBuildInfo -BuildManifest TestDrive:\MyModule\Source\build.psd1 -BuildCommandInvocation @{ - MyCommand = @{ - Parameters = @{ - Encoding = @{ParameterType = "string" } - Target = @{ParameterType = "string" } - SourcePath = @{ParameterType = "string" } - SourceDirectories = @{ParameterType = "string[]" } - OutputDirectory = @{ParameterType = "string" } + GetBuildInfo -BuildManifest $TestDrive/MyModule/Source/build.psd1 -BuildCommandInvocation @{ + MyCommand = @{ + Parameters = @{ + Encoding = @{ParameterType = "string" } + Target = @{ParameterType = "string" } + SourcePath = @{ParameterType = "string" } + SourceDirectories = @{ParameterType = "string[]" } + OutputDirectory = @{ParameterType = "string" } + } + } + BoundParameters = @{ + OutputDirectory = '../ridiculoustestvalue' } } - BoundParameters = @{ - OutputDirectory = '..\ridiculoustestvalue' - } - } - } - - It "Parses the build.psd1" { - Assert-MockCalled Import-Metadata -ModuleName ModuleBuilder -ParameterFilter { - (Convert-FolderSeparator $Path) -eq (Convert-FolderSeparator "TestDrive:\MyModule\Source\build.psd1") } } It "Reads bound parameters from the BuildCommandInvocation" { - $Result.OutputDirectory |Should -Be "..\ridiculoustestvalue" + $Result.OutputDirectory |Should -Be "../ridiculoustestvalue" } It "Returns the resolved Module path, SourceDirectories, and overridden OutputDirectory (via Invocation param)" { # if set in build.psd1 it will stay the same (i.e. relative) (Convert-FolderSeparator $Result.SourcePath) | - Should -Be (Convert-FolderSeparator "TestDrive:\MyModule\Source\MyModule.psd1") + Should -Be (Convert-FolderSeparator "$TestDrive/MyModule/Source/MyModule.psd1") $Result.SourceDirectories | Should -Be @("Classes", "Public") - $Result.OutputDirectory | Should -Be '..\ridiculoustestvalue' + $Result.OutputDirectory | Should -Be '../ridiculoustestvalue' } } Context 'Error when calling GetBuildInfo the wrong way' { It 'Should throw if the SourcePath does not exist' { - {InModuleScope -ModuleName ModuleBuilder { - GetBuildInfo -BuildManifest TestDrive:\NOTEXIST\Source\build.psd1 + {InModuleScope ModuleBuilder { + GetBuildInfo -BuildManifest $TestDrive/NOTEXIST/Source/build.psd1 }} | Should -Throw } It 'Should throw if the Build Manifest does not point to a build.psd1 file' { - {InModuleScope -ModuleName ModuleBuilder { - GetBuildInfo -BuildManifest TestDrive:\NOTEXIST\Source\ERROR.psd1 + {InModuleScope ModuleBuilder { + GetBuildInfo -BuildManifest $TestDrive/NOTEXIST/Source/ERROR.psd1 }} | Should -Throw } It 'Should throw if the Module manifest does not exist' { # use -Force to create the subdirectories - New-Item -Force TestDrive:\NoModuleManifest\Source\build.psd1 -ItemType File - {InModuleScope -ModuleName ModuleBuilder { - GetBuildInfo -BuildManifest TestDrive:\NoModuleManifest\Source\build.psd1 + New-Item -Force $TestDrive/NoModuleManifest/Source/build.psd1 -ItemType File + {InModuleScope ModuleBuilder { + GetBuildInfo -BuildManifest $TestDrive/NoModuleManifest/Source/build.psd1 }} | Should -Throw } } diff --git a/Tests/Private/GetRelativePath.Tests.ps1 b/Tests/Private/GetRelativePath.Tests.ps1 index e11aae9..2c4d219 100644 --- a/Tests/Private/GetRelativePath.Tests.ps1 +++ b/Tests/Private/GetRelativePath.Tests.ps1 @@ -1,7 +1,9 @@ -#requires -Module ModuleBuilder + Describe "GetRelativePath" { - . $PSScriptRoot\..\Convert-FolderSeparator.ps1 - $CommandInfo = InModuleScope ModuleBuilder { Get-Command GetRelativePath } + BeforeAll { + . $PSScriptRoot\..\Convert-FolderSeparator.ps1 + $CommandInfo = InModuleScope ModuleBuilder { Get-Command GetRelativePath } + } Context "All Parameters are mandatory" { @@ -23,34 +25,36 @@ Describe "GetRelativePath" { # I'm not going to bother writing tests for this other than "it's the same as .NET's" if ([System.IO.Path]::GetRelativePath) { Context "The output always matches [System.IO.Path]::GetRelativePath" { - $TestCases = @( - @{ RelativeTo = "G:\Module"; Path = "G:\Module\Source" } - @{ RelativeTo = "G:\Module"; Path = "G:\Module\Source\Public" } - @{ RelativeTo = "G:\Module\Source"; Path = "G:\Module\Output" } - @{ RelativeTo = "G:\Module\Source"; Path = "G:\Module\Output\" } - @{ RelativeTo = "G:\Module\Source\"; Path = "G:\Module\Output\" } - @{ RelativeTo = "G:\Module\Source\"; Path = "G:\Module\Output" } - @{ RelativeTo = "G:\Projects\Modules\MyModule\Source\Public"; Path = "G:\Modules\MyModule" } - @{ RelativeTo = "G:\Projects\Modules\MyModule\Source\Public"; Path = "G:\Projects\Modules\MyModule" } - # These ones are backwards, but they still work - @{ RelativeTo = "G:\Module\Source" ; Path = "G:\Module" } - @{ RelativeTo = "G:\Module\Source\Public"; Path = "G:\Module" } - # These are linux-like: - @{ RelativeTo = "/mnt/c/Users/Jaykul/Projects/Modules/ModuleBuilder"; Path = "/mnt/c/Users/Jaykul/Projects/Modules/ModuleBuilder/Source"; } - @{ RelativeTo = "/mnt/c/Users/Jaykul/Projects/Modules/ModuleBuilder"; Path = "/mnt/c/Users/Jaykul/Projects/Output"; } - @{ RelativeTo = "/mnt/c/Users/Jaykul/Projects/Modules/ModuleBuilder"; Path = "/mnt/c/Users/Jaykul/Projects/"; } - # Weird PowerShell Paths - @{ RelativeTo = "TestDrive:/Projects/Modules/ModuleBuilder"; Path = "TestDrive:\Projects" } - @{ RelativeTo = "TestDrive:/Projects/Modules/ModuleBuilder"; Path = "TestDrive:/Projects" } - @{ RelativeTo = "TestDrive:/Projects"; Path = "TestDrive:/Projects/Modules/ModuleBuilder" } - ) - - # On Windows, there's a shortcut when the path points to totally different drive letters: - if ($PSVersionTable.Platform -eq "Win32NT") { - $TestCases += @( - @{ RelativeTo = "G:\Projects\Modules\MyModule\Source\Public"; Path = "C:\Modules\MyModule" } - @{ RelativeTo = "G:\Projects\Modules\MyModule\Source\Public"; Path = "F:\Projects\Modules\MyModule" } + BeforeDiscovery { + $TestCases = @( + @{ RelativeTo = "G:\Module"; Path = "G:\Module\Source" } + @{ RelativeTo = "G:\Module"; Path = "G:\Module\Source\Public" } + @{ RelativeTo = "G:\Module\Source"; Path = "G:\Module\Output" } + @{ RelativeTo = "G:\Module\Source"; Path = "G:\Module\Output\" } + @{ RelativeTo = "G:\Module\Source\"; Path = "G:\Module\Output\" } + @{ RelativeTo = "G:\Module\Source\"; Path = "G:\Module\Output" } + @{ RelativeTo = "G:\Projects\Modules\MyModule\Source\Public"; Path = "G:\Modules\MyModule" } + @{ RelativeTo = "G:\Projects\Modules\MyModule\Source\Public"; Path = "G:\Projects\Modules\MyModule" } + # These ones are backwards, but they still work + @{ RelativeTo = "G:\Module\Source" ; Path = "G:\Module" } + @{ RelativeTo = "G:\Module\Source\Public"; Path = "G:\Module" } + # These are linux-like: + @{ RelativeTo = "/mnt/c/Users/Jaykul/Projects/Modules/ModuleBuilder"; Path = "/mnt/c/Users/Jaykul/Projects/Modules/ModuleBuilder/Source"; } + @{ RelativeTo = "/mnt/c/Users/Jaykul/Projects/Modules/ModuleBuilder"; Path = "/mnt/c/Users/Jaykul/Projects/Output"; } + @{ RelativeTo = "/mnt/c/Users/Jaykul/Projects/Modules/ModuleBuilder"; Path = "/mnt/c/Users/Jaykul/Projects/"; } + # Weird PowerShell Paths + @{ RelativeTo = "TestDrive:/Projects/Modules/ModuleBuilder"; Path = "TestDrive:\Projects" } + @{ RelativeTo = "TestDrive:/Projects/Modules/ModuleBuilder"; Path = "TestDrive:/Projects" } + @{ RelativeTo = "TestDrive:/Projects"; Path = "TestDrive:/Projects/Modules/ModuleBuilder" } ) + + # On Windows, there's a shortcut when the path points to totally different drive letters: + if ($PSVersionTable.Platform -eq "Win32NT") { + $TestCases += @( + @{ RelativeTo = "G:\Projects\Modules\MyModule\Source\Public"; Path = "C:\Modules\MyModule" } + @{ RelativeTo = "G:\Projects\Modules\MyModule\Source\Public"; Path = "F:\Projects\Modules\MyModule" } + ) + } } It "Returns the same result as Path.GetRelativePath for " -TestCases $TestCases { diff --git a/Tests/Private/ImportBase64Module.Tests.ps1 b/Tests/Private/ImportBase64Module.Tests.ps1 new file mode 100644 index 0000000..fe96371 --- /dev/null +++ b/Tests/Private/ImportBase64Module.Tests.ps1 @@ -0,0 +1,38 @@ +Describe "ImportBase64Module" { + BeforeAll { + $PSDefaultParameterValues = @{ + "Mock:ModuleName" = "ModuleBuilder" + "Assert-MockCalled:ModuleName" = "ModuleBuilder" + } + + $Source = Convert-Path (Join-Path $PSScriptRoot ../Integration/Source1/Public/Set-Source.ps1) + + $CommandUnderTest, $Base64 = InModuleScope ModuleBuilder { + Get-Command ImportBase64Module + CompressToBase64 (Join-Path $PSScriptRoot ../Integration/Source1/Public/Set-Source.ps1) -Debug + } + + $Plain = Get-Content $Source -Raw + + } + + It "Calls New-Module with the decompressed script" { + Mock New-Module + & $CommandUnderTest $Base64 + + Assert-MockCalled New-Module -ParameterFilter { + "$ScriptBlock" -eq $Plain + } + } + + It "Calls Import-Module with the new module" { + Mock Import-Module + & $CommandUnderTest $Base64 + + Assert-MockCalled Import-Module -ParameterFilter { + $ModuleInfo[0].Definition -eq $Plain + } + } + + # TODO: Test assemblies +} diff --git a/Tests/Private/ImportModuleManifest.Tests.ps1 b/Tests/Private/ImportModuleManifest.Tests.ps1 index 7c7dbf1..0b02e33 100644 --- a/Tests/Private/ImportModuleManifest.Tests.ps1 +++ b/Tests/Private/ImportModuleManifest.Tests.ps1 @@ -1,8 +1,9 @@ -#requires -Module ModuleBuilder Describe "ImportModuleManifest" { Context "Mandatory Parameter" { - $CommandInfo = InModuleScope ModuleBuilder { Get-Command ImportModuleManifest } + BeforeAll { + $CommandInfo = InModuleScope ModuleBuilder { Get-Command ImportModuleManifest } + } It 'has a mandatory Path parameter for the PSPath by pipeline' { $Path = $CommandInfo.Parameters['Path'] diff --git a/Tests/Private/InitializeBuild.Tests.ps1 b/Tests/Private/InitializeBuild.Tests.ps1 index b48427e..9673f92 100644 --- a/Tests/Private/InitializeBuild.Tests.ps1 +++ b/Tests/Private/InitializeBuild.Tests.ps1 @@ -1,24 +1,24 @@ -#requires -Module ModuleBuilder Describe "InitializeBuild" { - . $PSScriptRoot\..\Convert-FolderSeparator.ps1 - + BeforeAll { + . $PSScriptRoot\..\Convert-FolderSeparator.ps1 + } Context "It collects the initial data" { + BeforeAll { + # Note that "Path" is an alias for "SourcePath" + New-Item "TestDrive:\build.psd1" -Type File -Force -Value '@{ + Path = ".\Source\MyModule.psd1" + SourceDirectories = @("Classes", "Private", "Public") + }' - # Note that "Path" is an alias for "SourcePath" - New-Item "TestDrive:\build.psd1" -Type File -Force -Value '@{ - Path = ".\Source\MyModule.psd1" - SourceDirectories = @("Classes", "Private", "Public") - }' - - New-Item "TestDrive:\Source\" -Type Directory + New-Item "TestDrive:\Source\" -Type Directory - New-ModuleManifest "TestDrive:\Source\MyModule.psd1" -RootModule "MyModule.psm1" -Author "Test Manager" -ModuleVersion "1.0.0" - - $Result = @{} + New-ModuleManifest "TestDrive:\Source\MyModule.psd1" -RootModule "MyModule.psm1" -Author "Test Manager" -ModuleVersion "1.0.0" + $Result = @{} + } It "Handles Build-Module parameters, and the build.psd1 configuration" { Push-Location TestDrive:\ - $Result.Result = InModuleScope -ModuleName ModuleBuilder { + $Result.Result = InModuleScope ModuleBuilder { function Test-Build { [CmdletBinding()] param( @@ -38,35 +38,33 @@ Describe "InitializeBuild" { } } - Test-Build 'TestDrive:\' -Suffix "Export-ModuleMember *" + Test-Build 'TestDrive:\' -Suffix "Export-ModuleMember *" -WarningAction SilentlyContinue } Pop-Location $Result.Result | Should -Not -BeOfType [System.Management.Automation.ErrorRecord] } - $Result = $Result.Result - # $Result | Format-List * -Force | Out-Host It "Returns the ModuleInfo combined with the BuildInfo" { - $Result.Name | Should -Be "MyModule" - $Result.SourceDirectories | Should -Be @("Classes", "Private", "Public") - (Convert-FolderSeparator $Result.ModuleBase) | Should -Be (Convert-FolderSeparator "TestDrive:\Source") - (Convert-FolderSeparator $Result.SourcePath) | Should -Be (Convert-FolderSeparator "TestDrive:\Source\MyModule.psd1") + $Result.Result.Name | Should -Be "MyModule" + $Result.Result.SourceDirectories | Should -Be @("Classes", "Private", "Public") + (Convert-FolderSeparator $Result.Result.ModuleBase) | Should -Be (Convert-FolderSeparator "TestDrive:\Source") + (Convert-FolderSeparator $Result.Result.SourcePath) | Should -Be (Convert-FolderSeparator "TestDrive:\Source\MyModule.psd1") } It "Returns default values from the Build Command" { - $Result.OutputDirectory | Should -Be ".\Output" + $Result.Result.OutputDirectory | Should -Be ".\Output" } It "Returns overriden values from the build manifest" { - $Result.SourceDirectories | Should -Be @("Classes", "Private", "Public") + $Result.Result.SourceDirectories | Should -Be @("Classes", "Private", "Public") } It "Returns overriden values from parameters" { - $Result.SourcePath | Should -Be (Convert-Path 'TestDrive:\Source\MyModule.psd1') + $Result.Result.SourcePath | Should -Be (Convert-Path 'TestDrive:\Source\MyModule.psd1') } It "Sets VersionedOutputDirectory FALSE when UnversionedOutputDirectory is TRUE" { - $Result.VersionedOutputDirectory | Should -Be $false + $Result.Result.VersionedOutputDirectory | Should -Be $false } } } diff --git a/Tests/Private/ParseLineNumber.Tests.ps1 b/Tests/Private/ParseLineNumber.Tests.ps1 index 40fa5f2..16521a5 100644 --- a/Tests/Private/ParseLineNumber.Tests.ps1 +++ b/Tests/Private/ParseLineNumber.Tests.ps1 @@ -1,4 +1,3 @@ -#requires -Module ModuleBuilder Describe "ParseLineNumber" { It 'Should get the SourceFile and LineNumber from stack trace messages with modules' { @@ -45,7 +44,7 @@ Describe "ParseLineNumber" { $Source.SourceLineNumber | Should -Be 14 } - It 'Should get and and line number from errors at the console' { + It 'Should get ScriptBlock and No file and line number from errors at the console' { $Source = InModuleScope ModuleBuilder { ParseLineNumber "at , : line 1" } $Source.SourceFile | Should -Be "" diff --git a/Tests/Private/ResolveBuildManifest.Tests.ps1 b/Tests/Private/ResolveBuildManifest.Tests.ps1 index be68129..179ca1d 100644 --- a/Tests/Private/ResolveBuildManifest.Tests.ps1 +++ b/Tests/Private/ResolveBuildManifest.Tests.ps1 @@ -1,4 +1,3 @@ -#requires -Module ModuleBuilder Describe "ResolveBuildManifest" { # use the integration test code BeforeAll { diff --git a/Tests/Private/ResolveOutputFolder.Tests.ps1 b/Tests/Private/ResolveOutputFolder.Tests.ps1 index 40b95b0..c85e698 100644 --- a/Tests/Private/ResolveOutputFolder.Tests.ps1 +++ b/Tests/Private/ResolveOutputFolder.Tests.ps1 @@ -1,59 +1,64 @@ -#requires -Module ModuleBuilder Describe "ResolveOutputFolder" { . $PSScriptRoot\..\Convert-FolderSeparator.ps1 - $CommandInTest = InModuleScope ModuleBuilder { Get-Command ResolveOutputFolder } - filter ToTestDrive { "$_".Replace($TestDrive, "TestDrive:") } - - $TestCases = [Hashtable[]]@( - @{ # Be like Jaykul - Source = "ModuleName/Source" - Output = "ModuleName" - Result = "ModuleName/1.2.3" - Forced = "ModuleName/1.2.3" - } - @{ # Be like azure - Source = "1/s" - Output = "1/b" - Result = "1/b/ModuleName" - Forced = "1/b/ModuleName/1.2.3" - } - @{ # The default option would be Module/Source build to Module/Output - Source = "ModuleName/Source" - Output = "ModuleName/Output" - Result = "ModuleName/Output/ModuleName" - Forced = "ModuleName/Output/ModuleName/1.2.3" - } - @{ # Which is the same even without the common named parent - Source = "Source" - Output = "Output" - Result = "Output/ModuleName" - Forced = "Output/ModuleName/1.2.3" - } - @{ # An edge case, build straight to a modules folder - Source = "ModuleName/Source" - Output = "Modules" - Result = "Modules/ModuleName" - Forced = "Modules/ModuleName/1.2.3" - } - @{ # What if they pass in the correct path ahead of time? - Source = "1/s" - Output = "1/b/ModuleName" - Result = "1/b/ModuleName" - Forced = "1/b/ModuleName/1.2.3" - } - @{ # What if they pass in the correct path ahead of time? - Source = "1/s" - Output = "1/b/ModuleName/1.2.3" - Result = "1/b/ModuleName/1.2.3" - Forced = "1/b/ModuleName/1.2.3" - } - @{ # Super edge case: what if they pass in an incorrectly versioned output path? - Source = "1/s" - Output = "1/b/ModuleName/4.5.6" - Result = "1/b/ModuleName/4.5.6/ModuleName" - Forced = "1/b/ModuleName/4.5.6/ModuleName/1.2.3" - } - ) + BeforeAll { + filter ToTestDrive { "$_".Replace($TestDrive, "TestDrive:") } + + $CommandInTest = InModuleScope ModuleBuilder { Get-Command ResolveOutputFolder } + } + + BeforeDiscovery { + + $TestCases = [Hashtable[]]@( + @{ # Be like Jaykul + Source = "ModuleName/Source" + Output = "ModuleName" + Result = "ModuleName/1.2.3" + Forced = "ModuleName/1.2.3" + } + @{ # Be like azure + Source = "1/s" + Output = "1/b" + Result = "1/b/ModuleName" + Forced = "1/b/ModuleName/1.2.3" + } + @{ # The default option would be Module/Source build to Module/Output + Source = "ModuleName/Source" + Output = "ModuleName/Output" + Result = "ModuleName/Output/ModuleName" + Forced = "ModuleName/Output/ModuleName/1.2.3" + } + @{ # Which is the same even without the common named parent + Source = "Source" + Output = "Output" + Result = "Output/ModuleName" + Forced = "Output/ModuleName/1.2.3" + } + @{ # An edge case, build straight to a modules folder + Source = "ModuleName/Source" + Output = "Modules" + Result = "Modules/ModuleName" + Forced = "Modules/ModuleName/1.2.3" + } + @{ # What if they pass in the correct path ahead of time? + Source = "1/s" + Output = "1/b/ModuleName" + Result = "1/b/ModuleName" + Forced = "1/b/ModuleName/1.2.3" + } + @{ # What if they pass in the correct path ahead of time? + Source = "1/s" + Output = "1/b/ModuleName/1.2.3" + Result = "1/b/ModuleName/1.2.3" + Forced = "1/b/ModuleName/1.2.3" + } + @{ # Super edge case: what if they pass in an incorrectly versioned output path? + Source = "1/s" + Output = "1/b/ModuleName/4.5.6" + Result = "1/b/ModuleName/4.5.6/ModuleName" + Forced = "1/b/ModuleName/4.5.6/ModuleName/1.2.3" + } + ) + } Context "Build ModuleName" { It "From '' to '' creates ''" -TestCases $TestCases { param($Source, $Output, $Result) @@ -125,7 +130,7 @@ Describe "ResolveOutputFolder" { Output = Convert-FolderSeparator "TestDrive:/ModuleName/" } - { &$CommandInTest @Parameters -Name ModuleName -Target Build -Version 1.2.3 -Force } | Should -throw "There is a file in the way" + { &$CommandInTest @Parameters -Name ModuleName -Target Build -Version 1.2.3 -Force } | Should -throw "*There is a file in the way*" } } } diff --git a/Tests/Private/SetModuleContent.Tests.ps1 b/Tests/Private/SetModuleContent.Tests.ps1 index 33a6a98..5112162 100644 --- a/Tests/Private/SetModuleContent.Tests.ps1 +++ b/Tests/Private/SetModuleContent.Tests.ps1 @@ -1,7 +1,16 @@ Describe "SetModuleContent" { + BeforeAll { + $PSDefaultParameterValues = @{ + "Mock:ModuleName" = "ModuleBuilder" + "InModuleScope:ModuleName" = "ModuleBuilder" + "Assert-MockCalled:ModuleName" = "ModuleBuilder" + } + } Context "Necessary Parameters" { - $CommandInfo = InModuleScope ModuleBuilder { Get-Command SetModuleContent } + BeforeAll { + $CommandInfo = InModuleScope ModuleBuilder { Get-Command SetModuleContent } + } It "has a mandatory string OutputPath parameter" { $OutputPath = $CommandInfo.Parameters['OutputPath'] @@ -30,41 +39,28 @@ Describe "SetModuleContent" { $Encoding.ParameterType | Should -Be ([String]) $Encoding.Attributes.Where{$_ -is [Parameter]}.Mandatory | Should -Be $False } - - $CommandInfo.Parameters['OutputPath'] + AfterAll { + $CommandInfo.Parameters['OutputPath'] + } } Context "Joining files into one" { - ${global:mock get content index} = 1 - - Mock Get-Content -ModuleName ModuleBuilder { - "Script Content" - "File $((${global:mock get content index}++))" - "From $Path" - } - - Mock Resolve-Path -ModuleName ModuleBuilder { - $path -replace "TestDrive:\\", ".\" - } -ParameterFilter { $Relative } - - - - InModuleScope ModuleBuilder { - $Files = "TestDrive:\Private\First.ps1", - "TestDrive:\Private\Second.ps1", - "TestDrive:\Public\Third.ps1" - SetModuleContent -Source $Files -Output TestDrive:\Output.psm1 - } - - It "Calls get-content on every source file" { - Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Private\First.ps1" } - Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Private\Second.ps1" } - Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Public\Third.ps1" } + BeforeAll { + New-Item $TestDrive/Private/First.ps1 -Force -ItemType File -Value "File 1`nFirst.ps1" + New-Item $TestDrive/Private/Second.ps1 -Force -ItemType File -Value "File 2`nSecond.ps1" + New-Item $TestDrive/Public/Third.ps1 -Force -ItemType File -Value "File 3`nThird.ps1" + + InModuleScope ModuleBuilder { + $Files = "$TestDrive/Private/First.ps1", + "$TestDrive/Private/Second.ps1", + "$TestDrive/Public/Third.ps1" + SetModuleContent -Source $Files -Output $TestDrive/Output.psm1 + } } It "Copies all three files into the Output" { - $Content = Get-Content TestDrive:\Output.psm1 -Raw + $Content = Get-Content $TestDrive/Output.psm1 -Raw $Content | Should -Match "File 1" $Content | Should -Match "First.ps1" @@ -77,40 +73,23 @@ Describe "SetModuleContent" { } Context "Supports adding Prefix and Suffix content" { - ${global:mock get content index} = 1 - - Mock Get-Content -ModuleName ModuleBuilder { - "Script Content" - "File $((${global:mock get content index}++))" - "From $Path" - } - - Mock Resolve-Path -ModuleName ModuleBuilder { - if ($path -match "TestDrive:") { - $path -replace "TestDrive:\\", ".\" - } else { - write-error "$path not found" + BeforeAll { + New-Item $TestDrive/Private/First.ps1 -Force -ItemType File -Value "File 1`nFirst.ps1" + New-Item $TestDrive/Private/Second.ps1 -Force -ItemType File -Value "File 2`nSecond.ps1" + New-Item $TestDrive/Public/Third.ps1 -Force -ItemType File -Value "File 3`nThird.ps1" + + InModuleScope ModuleBuilder { + $Files = "using module Configuration", + "$TestDrive/Private/First.ps1", + "$TestDrive/Private/Second.ps1", + "$TestDrive/Public/Third.ps1", + "Export-ModuleMember Stuff" + SetModuleContent -Source $Files -Output $TestDrive/Output.psm1 } - } -ParameterFilter { $Relative } - - - InModuleScope ModuleBuilder { - $Files = "using module Configuration", - "TestDrive:\Private\First.ps1", - "TestDrive:\Private\Second.ps1", - "TestDrive:\Public\Third.ps1", - "Export-ModuleMember Stuff" - SetModuleContent -Source $Files -Output TestDrive:\Output.psm1 - } - - It "Calls get-content on every source file" { - Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Private\First.ps1" } - Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Private\Second.ps1" } - Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Public\Third.ps1" } } It "Copies all three files into the Output" { - $Content = Get-Content TestDrive:\Output.psm1 -Raw + $Content = Get-Content $TestDrive/Output.psm1 -Raw $Content | Should -Match "File 1" $Content | Should -Match "First.ps1" @@ -122,53 +101,36 @@ Describe "SetModuleContent" { } It "Includes the prefix" { - $Content = Get-Content TestDrive:\Output.psm1 -Raw + $Content = Get-Content $TestDrive/Output.psm1 -Raw $Content | Should -Match "#Region 'PREFIX' -1" $Content | Should -Match "using module Configuration" } It "Includes the suffix" { - $Content = Get-Content TestDrive:\Output.psm1 -Raw + $Content = Get-Content $TestDrive/Output.psm1 -Raw $Content | Should -Match "#Region 'SUFFIX' -1" $Content | Should -Match "Export-ModuleMember Stuff" } } Context "Adds a newline before the content of each script file" { - ${global:mock get content index} = 1 - - Mock Get-Content -ModuleName ModuleBuilder { - "Script Content" - "File $((${global:mock get content index}++))" - "From $Path" - } - - Mock Resolve-Path -ModuleName ModuleBuilder { - if ($path -match "TestDrive:") { - $path -replace "TestDrive:\\", ".\" - } else { - write-error "$path not found" + BeforeAll { + New-Item $TestDrive/Private/First.ps1 -Force -ItemType File -Value "File 1`nFirst.ps1" + New-Item $TestDrive/Private/Second.ps1 -Force -ItemType File -Value "File 2`nSecond.ps1" + New-Item $TestDrive/Public/Third.ps1 -Force -ItemType File -Value "File 3`nThird.ps1" + + InModuleScope ModuleBuilder { + $Files = "using module Configuration", + "$TestDrive/Private/First.ps1", + "$TestDrive/Private/Second.ps1", + "$TestDrive/Public/Third.ps1", + "Export-ModuleMember Stuff" + SetModuleContent -Source $Files -Output $TestDrive/Output.psm1 } - } -ParameterFilter { $Relative } - - - InModuleScope ModuleBuilder { - $Files = "using module Configuration", - "TestDrive:\Private\First.ps1", - "TestDrive:\Private\Second.ps1", - "TestDrive:\Public\Third.ps1", - "Export-ModuleMember Stuff" - SetModuleContent -Source $Files -Output TestDrive:\Output.psm1 } - It "Calls get-content on every source file" { - Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Private\First.ps1" } - Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Private\Second.ps1" } - Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Public\Third.ps1" } - } - - It "Copies all three files into the Output" { - $Content = Get-Content TestDrive:\Output.psm1 -Raw + It "Copies the content of all the source files to the output" { + $Content = Get-Content $TestDrive/Output.psm1 -Raw $Content | Should -Match "File 1" $Content | Should -Match "First.ps1" @@ -181,11 +143,11 @@ Describe "SetModuleContent" { It "Include a new line before the content of each script file" { # Replacing CRLF to LF to support cross-platform testing. - $Content = (Get-Content TestDrive:\Output.psm1 -Raw) -replace '\r?\n', "`n" + $Content = (Get-Content $TestDrive/Output.psm1 -Raw) -replace '\r?\n', "`n" - $Content | Should -Match "\#Region\ '\.\\Private\\First\.ps1'\ -1\n{2,}" - $Content | Should -Match "\#Region\ '\.\\Private\\Second\.ps1'\ -1\n{2,}" - $Content | Should -Match "\#Region\ '\.\\Public\\Third\.ps1'\ -1\n{2,}" + $Content | Should -Match "\#Region\ '.*[\\/]Private[\\/]First\.ps1'\ -1\n{2,}" + $Content | Should -Match "\#Region\ '.*[\\/]Private[\\/]Second\.ps1'\ -1\n{2,}" + $Content | Should -Match "\#Region\ '.*[\\/]Public[\\/]Third\.ps1'\ -1\n{2,}" } } } diff --git a/Tests/Public/Build-Module.Tests.ps1 b/Tests/Public/Build-Module.Tests.ps1 index 268e9fe..f8bf791 100644 --- a/Tests/Public/Build-Module.Tests.ps1 +++ b/Tests/Public/Build-Module.Tests.ps1 @@ -1,28 +1,61 @@ -#requires -Module ModuleBuilder Describe "Build-Module" { - . $PSScriptRoot\..\Convert-FolderSeparator.ps1 - $PSDefaultParameterValues = @{ - "Mock:ModuleName" = "ModuleBuilder" - "Assert-MockCalled:ModuleName" = "ModuleBuilder" + BeforeAll { + . $PSScriptRoot\..\Convert-FolderSeparator.ps1 + + $PSDefaultParameterValues = @{ + "Mock:ModuleName" = "ModuleBuilder" + "Assert-MockCalled:ModuleName" = "ModuleBuilder" + } + + Mock Move-UsingStatement + Mock SetModuleContent + + Mock Update-Metadata + Mock Copy-Item + Mock Set-Location + + Mock Join-Path { + [IO.Path]::Combine($Path, $ChildPath) + } + + Mock Get-Metadata { + "First Release" + } + + $global:Mock_OutputPath = Convert-FolderSeparator "$TestDrive/Output/MyModule" + + Mock New-Item { [IO.DirectoryInfo]("$TestDrive/Output/MyModule") } -Parameter { + (Convert-FolderSeparator "$Path") -eq $Mock_OutputPath -and + $ItemType -eq "Directory" -and $Force + } + + Mock Test-Path { $True } -Parameter { + (Convert-FolderSeparator "$Path") -eq $Mock_OutputPath -and ($PathType -notin "Any", "Leaf") + } + + Mock Remove-Item -Parameter { + (Convert-FolderSeparator "$Path") -eq $Mock_OutputPath + } } Context "Parameter Binding" { - - $Parameters = (Get-Command Build-Module).Parameters + BeforeAll { + $Parameters = (Get-Command Build-Module).Parameters + } It "has an optional string parameter for the SourcePath" { $parameters.ContainsKey("SourcePath") | Should -Be $true $parameters["SourcePath"].ParameterType | Should -Be ([string]) - $parameters["SourcePath"].Attributes.Where{$_ -is [Parameter]}.Mandatory | Should -Be $false + $parameters["SourcePath"].Attributes.Where{ $_ -is [Parameter] }.Mandatory | Should -Be $false } It "throws if the SourcePath doesn't exist" { - { Build-Module -SourcePath TestDrive:/NoSuchPath } | Should -Throw "Source must point to a valid module" + { Build-Module -SourcePath "$TestDrive/NoSuchPath" } | Should -Throw "*Source must point to a valid module*" } It "has an optional string parameter for the OutputDirectory" { $parameters.ContainsKey("OutputDirectory") | Should -Be $true $parameters["OutputDirectory"].ParameterType | Should -Be ([string]) - $parameters["OutputDirectory"].Attributes.Where{$_ -is [Parameter]}.Mandatory | Should -Be $false + $parameters["OutputDirectory"].Attributes.Where{ $_ -is [Parameter] }.Mandatory | Should -Be $false } It "has an optional parameter for setting the Version" { @@ -35,26 +68,30 @@ Describe "Build-Module" { $parameters.ContainsKey("Encoding") | Should -Be $true # Note that in PS Core, we can't use encoding types for parameters $parameters["Encoding"].ParameterType | Should -Be ([string]) - $parameters["Encoding"].Attributes.Where{$_ -is [Parameter]}.Mandatory | Should -Be $false + $parameters["Encoding"].Attributes.Where{ $_ -is [Parameter] }.Mandatory | Should -Be $false } It "Warns if you set the encoding to anything but UTF8" { $warns = @() # Note: Using WarningAction Stop just to avoid testing anything else here ;) - try { Build-Module -Encoding ASCII -WarningAction Stop -WarningVariable +warns } catch {} + try { + Build-Module -Encoding ASCII -WarningAction Stop -WarningVariable +warns + } catch { + "Do nothing" + } $warns.Message | Should -Match "recommend you build your script modules with UTF8 encoding" } It "has an optional string parameter for a Prefix" { $parameters.ContainsKey("Prefix") | Should -Be $true $parameters["Prefix"].ParameterType | Should -Be ([string]) - $parameters["Prefix"].Attributes.Where{$_ -is [Parameter]}.Mandatory | Should -Be $false + $parameters["Prefix"].Attributes.Where{ $_ -is [Parameter] }.Mandatory | Should -Be $false } It "has an optional string parameter for a Suffix" { $parameters.ContainsKey("Suffix") | Should -Be $true $parameters["Suffix"].ParameterType | Should -Be ([string]) - $parameters["Suffix"].Attributes.Where{$_ -is [Parameter]}.Mandatory | Should -Be $false + $parameters["Suffix"].Attributes.Where{ $_ -is [Parameter] }.Mandatory | Should -Be $false } It "supports setting the Target to Clean, Build or both" { @@ -62,7 +99,7 @@ Describe "Build-Module" { # Techincally we could implement this a few other ways ... $parameters["Target"].ParameterType | Should -Be ([string]) - $parameters["Target"].Attributes.Where{$_ -is [ValidateSet]}.ValidValues | Should -Be "Clean", "Build", "CleanBuild" + $parameters["Target"].Attributes.Where{ $_ -is [ValidateSet] }.ValidValues | Should -Be "Clean", "Build", "CleanBuild" } It "supports an optional string array parameter CopyPaths (which used to be CopyDirectories)" { @@ -79,254 +116,225 @@ Describe "Build-Module" { } } - InModuleScope ModuleBuilder { - Mock MoveUsingStatements - Mock SetModuleContent - } - Mock Update-Metadata - Mock Copy-Item - Mock Set-Location - - Mock Join-Path { - [IO.Path]::Combine($Path, $ChildPath) - } - - Mock Get-Metadata { - "First Release" - } - - $global:Mock_OutputPath = Convert-FolderSeparator "TestDrive:/Output/MyModule" - - Mock New-Item { [IO.DirectoryInfo]("$TestDrive/Output/MyModule") } -Parameter { - (Convert-FolderSeparator "$Path") -eq $Mock_OutputPath -and - $ItemType -eq "Directory" -and $Force - } - - Mock Test-Path { $True } -Parameter { - (Convert-FolderSeparator "$Path") -eq $Mock_OutputPath -and ($PathType -notin "Any", "Leaf") - } - - Mock Remove-Item -Parameter { - (Convert-FolderSeparator "$Path") -eq $Mock_OutputPath - } - Context "When run without parameters" { - Push-Location TestDrive:/ -StackName BuildModuleTest - New-Item -ItemType Directory -Path TestDrive:/Output/MyModule/1.0.0/ -Force - - Mock ConvertToAst { - [PSCustomObject]@{ - PSTypeName = "PoshCode.ModuleBuilder.ParseResults" - ParseErrors = $null - Tokens = $null - AST = { }.AST + BeforeAll { + Push-Location $TestDrive/ -StackName BuildModuleTest + New-Item -ItemType Directory -Path $TestDrive/Output/MyModule/1.0.0/ -Force + + Mock ConvertToAst { + [PSCustomObject]@{ + PSTypeName = "PoshCode.ModuleBuilder.ParseResults" + ParseErrors = $null + Tokens = $null + AST = { }.AST + Path = $Path + } } - } - Mock GetCommandAlias { @{'Get-MyInfo' = @('GMI') } } - Mock InitializeBuild { - # These are actually all the values that we need - [PSCustomObject]@{ - OutputDirectory = "TestDrive:/Output" - Name = "MyModule" - Version = [Version]"1.0.0" - Target = "CleanBuild" - ModuleBase = "TestDrive:/MyModule/" - CopyDirectories = @() - Encoding = "UTF8" - PublicFilter = "Public/*.ps1" + Mock Update-AliasesToExport + Mock InitializeBuild { + # These are actually all the values that we need + [PSCustomObject]@{ + OutputDirectory = "$TestDrive/Output" + Name = "MyModule" + Version = [Version]"1.0.0" + Target = "CleanBuild" + ModuleBase = "$TestDrive/MyModule/" + CopyDirectories = @() + Encoding = "UTF8" + PublicFilter = "Public/*.ps1" + } } - } + Mock Push-Location {} - Mock Push-Location {} + # Fake no existing build, to be sure it rebuilds + Mock Get-Item { [PSCustomObject]@{ LastWriteTime = $null } } - # So it doesn't have to exist - Mock Convert-Path { $Path } - Mock Get-ChildItem { - [IO.FileInfo]"$TestDrive/Output/MyModule/Public/Get-MyInfo.ps1" + # So it doesn't have to exist + Mock Convert-Path { $Path } + Mock Get-ChildItem { + [IO.FileInfo]"$TestDrive/Output/MyModule/Public/Get-MyInfo.ps1" + } } - - - try { - Build-Module - } finally { + AfterAll { Pop-Location -StackName BuildModuleTest } + # NOTE: We're not just clearing output, but the whole folder It "Should remove the output folder if it exists" { - Assert-MockCalled Remove-Item + Build-Module + Assert-MockCalled Remove-Item -Scope Context } It "Should always (re)create the OutputDirectory" { - Assert-MockCalled New-Item + Assert-MockCalled New-Item -Scope Context } It "Should run in the module source folder" { Assert-MockCalled Push-Location -Parameter { - $Path -eq "TestDrive:/MyModule/" - } + $Path -eq "$TestDrive/MyModule/" + } -Scope Context } It "Should call ConvertToAst to parse the module" { - Assert-MockCalled ConvertToAst + Assert-MockCalled ConvertToAst -Scope Context } - It "Should call MoveUsingStatements to move the using statements, just in case" { - Assert-MockCalled MoveUsingStatements -Parameter { + It "Should call Move-UsingStatement to move the using statements" { + Assert-MockCalled Move-UsingStatement -Parameter { $AST.Extent.Text -eq "{ }" - } + } -Scope Context } It "Should call SetModuleContent to combine the source files" { - Assert-MockCalled SetModuleContent + Assert-MockCalled SetModuleContent -Scope Context } It "Should call Update-Metadata to set the FunctionsToExport" { Assert-MockCalled Update-Metadata -Parameter { $PropertyName -eq "FunctionsToExport" - } + } -Scope Context } - It "Should call Update-Metadata to set the AliasesToExport" { - Assert-MockCalled Update-Metadata -Parameter { - $PropertyName -eq "AliasesToExport" - } + It "Should call Update-AliasesToExport to update aliases" { + Assert-MockCalled Update-AliasesToExport -Parameter { + $AST.Extent.Text -eq "{ }" + } -Scope Context } } Context "When run without 'Clean' in the target" { - Push-Location TestDrive:/ -StackName BuildModuleTest - New-Item -ItemType Directory -Path TestDrive:/MyModule/Public -Force - New-Item -ItemType File -Path TestDrive:/MyModule/Public/Get-MyInfo.ps1 -Force - Start-Sleep -Milliseconds 200 # to ensure the output is after the input - New-Item -ItemType Directory -Path TestDrive:/1.0.0/ -Force - New-Item -ItemType File -Path TestDrive:/1.0.0/MyModule.psm1 -Force - - Mock InitializeBuild { - # These are actually all the values that we need - [PSCustomObject]@{ - OutputDirectory = "TestDrive:/Output" - Name = "MyModule" - Version = [Version]"1.0.0" - Target = $Target - ModuleBase = "TestDrive:/MyModule/" - CopyDirectories = @() - Encoding = "UTF8" - PublicFilter = "Public/*.ps1" + BeforeAll { + Push-Location $TestDrive -StackName BuildModuleTest + New-Item -ItemType Directory -Path $TestDrive/MyModule/Public -Force + New-Item -ItemType File -Path $TestDrive/MyModule/Public/Get-MyInfo.ps1 -Force + Start-Sleep -Milliseconds 200 # to ensure the output is after the input + New-Item -ItemType Directory -Path $TestDrive/1.0.0/ -Force + New-Item -ItemType File -Path $TestDrive/1.0.0/MyModule.psm1 -Force + + Mock InitializeBuild { + # These are actually all the values that we need + [PSCustomObject]@{ + OutputDirectory = "$TestDrive/Output" + Name = "MyModule" + Version = [Version]"1.0.0" + Target = "Build" + ModuleBase = "$TestDrive/MyModule/" + CopyDirectories = @() + Encoding = "UTF8" + PublicFilter = "Public/*.ps1" + } } - } - Mock Convert-Path { $Path } + Mock Convert-Path { $Path } - Mock Get-ChildItem { - [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" - } + Mock Get-ChildItem { + [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" + } - Mock Get-Item { - [PSCustomObject]@{ LastWriteTime = Get-Date } - } + Mock Get-Item { + [PSCustomObject]@{ LastWriteTime = Get-Date } + } - try { - Build-Module -Target Build - } finally { + Build-Module -Target "Build" + } + AfterAll { Pop-Location -StackName BuildModuleTest } # NOTE: We're not just clearing output, but the whole folder It "Should NOT remove the output folder" { - Assert-MockCalled Remove-Item -Times 0 + Assert-MockCalled Remove-Item -Times 0 -Scope Context } It "Should check the dates on the output" { - Assert-MockCalled Get-Item -Times 1 + Assert-MockCalled Get-Item -Times 1 -Scope Context } It "Should always (re)create the OutputDirectory" { - Assert-MockCalled New-Item -Times 1 + Assert-MockCalled New-Item -Times 1 -Scope Context } It "Should not rebuild the source files" { - Assert-MockCalled SetModuleContent -Times 0 + Assert-MockCalled SetModuleContent -Times 0 -Scope Context } } Context "Setting the version to a SemVer string" { - $SemVer = "1.0.0-beta03+sha.22c35ffff166f34addc49a3b80e622b543199cc5.Date.2018-10-11" - $global:ExpectedVersion = "1.0.0" - Push-Location TestDrive:/ -StackName BuildModuleTest - New-Item -ItemType Directory -Path TestDrive:/MyModule/ -Force - New-Item -ItemType Directory -Path "TestDrive:/Output/MyModule/$ExpectedVersion" -Force - - Mock ResolveBuildManifest { "TestDrive:/MyModule/build.psd1" } - - Mock GetBuildInfo { - [PSCustomObject]@{ - OutputDirectory = "TestDrive:/Output" - SourcePath = "TestDrive:/MyModule/" - SemVer = $SemVer - Target = $Target - CopyPaths = @() - Encoding = "UTF8" - PublicFilter = "Public/*.ps1" - VersionedOutputDirectory = $true + BeforeAll { + $SemVer = "1.0.0-beta03+sha.22c35ffff166f34addc49a3b80e622b543199cc5.Date.2018-10-11" + $global:ExpectedVersion = "1.0.0" + Push-Location $TestDrive/ -StackName BuildModuleTest + New-Item -ItemType Directory -Path $TestDrive/MyModule/ -Force + New-Item -ItemType Directory -Path "$TestDrive/Output/MyModule/$ExpectedVersion" -Force + + Mock ResolveBuildManifest { "$TestDrive/MyModule/build.psd1" } + + Mock GetBuildInfo { + [PSCustomObject]@{ + OutputDirectory = "$TestDrive/Output" + SourcePath = "$TestDrive/MyModule/" + SemVer = $SemVer + Target = "CleanBuild" + CopyPaths = @() + Encoding = "UTF8" + PublicFilter = "Public/*.ps1" + VersionedOutputDirectory = $true + } } - } - Mock ImportModuleManifest { - [PSCustomObject]@{ - Name = "MyModule" - ModuleBase = "TestDrive:/MyModule/" + Mock ImportModuleManifest { + [PSCustomObject]@{ + Name = "MyModule" + ModuleBase = "$TestDrive/MyModule/" + } } - } - $global:Mock_OutputPath = Convert-FolderSeparator "TestDrive:/Output/MyModule/$ExpectedVersion" + $global:Mock_OutputPath = Convert-FolderSeparator "$TestDrive/Output/MyModule/$ExpectedVersion" - Mock Get-ChildItem { - [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" - } + Mock Get-ChildItem { + [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" + } + + # Fake no existing build, to be sure it rebuilds + Mock Get-Item { [PSCustomObject]@{ LastWriteTime = $null } } - try { Build-Module -SemVer $SemVer - } catch { + } + AfterAll { Pop-Location -StackName BuildModuleTest - throw } It "Should build to an output folder with the simple version." { - Assert-MockCalled Remove-Item - Assert-MockCalled New-Item + Assert-MockCalled Remove-Item -Scope Context + Assert-MockCalled New-Item -Scope Context } It "Should update the module version to the simple version." { Assert-MockCalled Update-Metadata -ParameterFilter { $PropertyName -eq "ModuleVersion" -and $Value -eq $ExpectedVersion - } + } -Scope Context } It "Should update the module pre-release version" { Assert-MockCalled Update-Metadata -ParameterFilter { $PropertyName -eq "PrivateData.PSData.Prerelease" -and $Value -eq "beta03" - } + } -Scope Context } It "When there are simple release notes, it should insert a line with the module name and full semver" { Assert-MockCalled Update-Metadata -ParameterFilter { $PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and $Value -eq "MyModule v$($SemVer)`nFirst Release" - } + } -Scope Context } It "When there's no release notes, it should insert the module name and full semver" { # If there's no release notes, but it was left uncommented Mock Get-Metadata { "" } - try { - Build-Module -SemVer $SemVer - } catch { - Pop-Location -StackName BuildModuleTest - throw - } + Build-Module -SemVer $SemVer Assert-MockCalled Update-Metadata -ParameterFilter { $PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and $Value -eq "MyModule v$SemVer" - } + } -Scope it } It "When there's a prefix empty line, it should insert the module name and full semver the same way" { @@ -335,103 +343,92 @@ Describe "Build-Module" { Multi-line Release Notes With a prefix carriage return" } - try { - Build-Module -SemVer $SemVer - } catch { - Pop-Location -StackName BuildModuleTest - throw - } + Build-Module -SemVer $SemVer Assert-MockCalled Update-Metadata -ParameterFilter { $PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and $Value -eq " MyModule v$SemVer Multi-line Release Notes With a prefix carriage return" - } + } -Scope It } - - Pop-Location -StackName BuildModuleTest } Context "Setting the version and pre-release" { - # $SemVer = "1.0.0-beta03+sha.22c35ffff166f34addc49a3b80e622b543199cc5.Date.2018-10-11" - $SemVer = @{ - Version = "1.0.0" - Prerelease = "beta03" - BuildMetadata = "Sha.22c35ffff166f34addc49a3b80e622b543199cc5.Date.2018-10-11" - } - $global:ExpectedVersion = "1.0.0" - Push-Location TestDrive:/ -StackName BuildModuleTest - New-Item -ItemType Directory -Path TestDrive:/MyModule/ -Force - New-Item -ItemType Directory -Path "TestDrive:/Output/MyModule" -Force - - Mock ResolveBuildManifest { "TestDrive:/MyModule/build.psd1" } - - Mock GetBuildInfo { - [PSCustomObject]@{ - OutputDirectory = "TestDrive:/Output" - SourcePath = "TestDrive:/MyModule/" - Version = "1.0.0" - Prerelease = "beta03" - BuildMetadata = "Sha.22c35ffff166f34addc49a3b80e622b543199cc5.Date.2018-10-11" - Target = "CleanBuild" - CopyPaths = @() - Encoding = "UTF8" - PublicFilter = "Public/*.ps1" + BeforeAll { + # $SemVer = "1.0.0-beta03+sha.22c35ffff166f34addc49a3b80e622b543199cc5.Date.2018-10-11" + $SemVer = @{ + Version = "1.0.0" + Prerelease = "beta03" + BuildMetadata = "Sha.22c35ffff166f34addc49a3b80e622b543199cc5.Date.2018-10-11" } - } + $global:ExpectedVersion = "1.0.0" + Push-Location $TestDrive/ -StackName BuildModuleTest + New-Item -ItemType Directory -Path $TestDrive/MyModule/ -Force + New-Item -ItemType Directory -Path "$TestDrive/Output/MyModule" -Force + + Mock ResolveBuildManifest { "$TestDrive/MyModule/build.psd1" } - Mock ImportModuleManifest { - [PSCustomObject]@{ - Name = "MyModule" - ModuleBase = "TestDrive:/MyModule/" + Mock GetBuildInfo { + [PSCustomObject]@{ + OutputDirectory = "$TestDrive/Output" + SourcePath = "$TestDrive/MyModule/" + Version = "1.0.0" + Prerelease = "beta03" + BuildMetadata = "Sha.22c35ffff166f34addc49a3b80e622b543199cc5.Date.2018-10-11" + Target = "CleanBuild" + CopyPaths = @() + Encoding = "UTF8" + PublicFilter = "Public/*.ps1" + } } - } - $global:Mock_OutputPath = Convert-FolderSeparator "TestDrive:/Output/MyModule" - Mock Get-ChildItem { - [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" - } + Mock ImportModuleManifest { + [PSCustomObject]@{ + Name = "MyModule" + ModuleBase = "$TestDrive/MyModule/" + } + } + + $global:Mock_OutputPath = Convert-FolderSeparator "$TestDrive/Output/MyModule" + Mock Get-ChildItem { + [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" + } + # Fake no existing build, to be sure it rebuilds + Mock Get-Item { [PSCustomObject]@{ LastWriteTime = $null } } - try { Build-Module @SemVer - } catch { + } + AfterAll { Pop-Location -StackName BuildModuleTest - throw } - It "Should build to an output folder with the simple version." { - Assert-MockCalled Remove-Item - Assert-MockCalled New-Item + Assert-MockCalled Remove-Item -Scope Context + Assert-MockCalled New-Item -Scope Context } It "Should update the module version to the simple version." { Assert-MockCalled Update-Metadata -ParameterFilter { $PropertyName -eq "ModuleVersion" -and $Value -eq $ExpectedVersion - } + } -Scope Context } It "Should update the module pre-release version" { Assert-MockCalled Update-Metadata -ParameterFilter { $PropertyName -eq "PrivateData.PSData.Prerelease" -and $Value -eq "beta03" - } + } -Scope Context } It "When there are simple release notes, it should insert a line with the module name and full semver" { Assert-MockCalled Update-Metadata -ParameterFilter { $PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and $Value -eq "MyModule v$($SemVer.Version)-$($SemVer.Prerelease)+$($SemVer.BuildMetadata)`nFirst Release" - } + } -Scope Context } It "When there's no release notes, it should insert the module name and full semver" { # If there's no release notes, but it was left uncommented Mock Get-Metadata { "" } - try { - Build-Module @SemVer - } catch { - Pop-Location -StackName BuildModuleTest - throw - } + Build-Module @SemVer Assert-MockCalled Update-Metadata -ParameterFilter { $PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and @@ -445,12 +442,7 @@ Describe "Build-Module" { Multi-line Release Notes With a prefix carriage return" } - try { - Build-Module @SemVer - } catch { - Pop-Location -StackName BuildModuleTest - throw - } + Build-Module @SemVer Assert-MockCalled Update-Metadata -ParameterFilter { $PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and $Value -eq " @@ -459,82 +451,79 @@ Describe "Build-Module" { With a prefix carriage return" } } - - Pop-Location -StackName BuildModuleTest } Context "Setting the version with no pre-release" { - # $SemVer = "1.0.0-beta03+sha.22c35ffff166f34addc49a3b80e622b543199cc5.Date.2018-10-11" - $SemVer = @{ - Version = "1.0.0" - BuildMetadata = "Sha.22c35ffff166f34addc49a3b80e622b543199cc5.Date.2018-10-11" - } - $global:ExpectedVersion = "1.0.0" - Push-Location TestDrive:/ -StackName BuildModuleTest - New-Item -ItemType Directory -Path TestDrive:/MyModule/ -Force - New-Item -ItemType Directory -Path "TestDrive:/Output/MyModule" -Force - - Mock InitializeBuild { - # These are actually all the values that we need - [PSCustomObject]@{ - OutputDirectory = "TestDrive:/Output" - Name = "MyModule" - Version = [Version]"1.0.0" - Target = $Target - ModuleBase = "TestDrive:/MyModule/" - CopyPaths = @() - Encoding = "UTF8" - PublicFilter = "Public/*.ps1" + BeforeAll { + # $SemVer = "1.0.0-beta03+sha.22c35ffff166f34addc49a3b80e622b543199cc5.Date.2018-10-11" + $SemVer = @{ + Version = "1.0.0" + BuildMetadata = "Sha.22c35ffff166f34addc49a3b80e622b543199cc5.Date.2018-10-11" + } + $global:ExpectedVersion = "1.0.0" + Push-Location $TestDrive/ -StackName BuildModuleTest + New-Item -ItemType Directory -Path $TestDrive/MyModule/ -Force + New-Item -ItemType Directory -Path "$TestDrive/Output/MyModule" -Force + + Mock InitializeBuild { + # These are actually all the values that we need + [PSCustomObject]@{ + OutputDirectory = "$TestDrive/Output" + Name = "MyModule" + Version = [Version]"1.0.0" + Target = "CleanBuild" + ModuleBase = "$TestDrive/MyModule/" + CopyPaths = @() + Encoding = "UTF8" + PublicFilter = "Public/*.ps1" + } } - } - Mock Convert-Path { $Path } - $global:Mock_OutputPath = Convert-FolderSeparator "TestDrive:/Output/MyModule" + Mock Convert-Path { $Path } + $global:Mock_OutputPath = Convert-FolderSeparator "$TestDrive/Output/MyModule" - Mock Get-ChildItem { - [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" - } + Mock Get-ChildItem { + [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" + } - try { Build-Module @SemVer - } catch { + } + + AfterAll { Pop-Location -StackName BuildModuleTest - throw } It "Should build to an output folder with the simple version." { - Assert-MockCalled Remove-Item - Assert-MockCalled New-Item + Assert-MockCalled Remove-Item -Scope Context + Assert-MockCalled New-Item -Scope Context } It "Should update the module version to the simple version." { Assert-MockCalled Update-Metadata -ParameterFilter { $PropertyName -eq "ModuleVersion" -and $Value -eq $ExpectedVersion - } + } -Scope Context } It "Should not change the module pre-release value" { Assert-MockCalled Update-Metadata -Times 0 -ParameterFilter { $PropertyName -eq "PrivateData.PSData.Prerelease" - } + } -Scope Context } - - Pop-Location -StackName BuildModuleTest } Context "Bug #70 Cannot build 1.2.3-pre-release" { BeforeEach { - Push-Location TestDrive:/ -StackName BuildModuleTest - New-Item -ItemType Directory -Path TestDrive:/MyModule/ -Force - New-Item -ItemType Directory -Path "TestDrive:/$ExpectedVersion/" -Force + Push-Location $TestDrive/ -StackName BuildModuleTest + New-Item -ItemType Directory -Path $TestDrive/MyModule/ -Force + New-Item -ItemType Directory -Path "$TestDrive/$ExpectedVersion/" -Force Mock GetBuildInfo { # These are actually all the values that we need [PSCustomObject]@{ - OutputDirectory = "TestDrive:/$Version" + OutputDirectory = "$TestDrive/1.2.3" Name = "MyModule" - Version = $Version - PreRelease = $PreRelease - Target = $Target - SourcePath = "TestDrive:/MyModule/" + Version = "1.2.3" + PreRelease = "pre-release" + Target = "CleanBuild" + SourcePath = "$TestDrive/MyModule/" CopyPaths = @() Encoding = "UTF8" PublicFilter = "Public/*.ps1" @@ -544,11 +533,11 @@ Describe "Build-Module" { Mock ImportModuleManifest { [PSCustomObject]@{ Name = "MyModule" - ModuleBase = "TestDrive:/MyModule/" + ModuleBase = "$TestDrive/MyModule/" } } - $global:Mock_OutputPath = Convert-FolderSeparator "TestDrive:/MyModule/$ExpectedVersion" + $global:Mock_OutputPath = Convert-FolderSeparator "$TestDrive/MyModule/$ExpectedVersion" Mock Get-ChildItem { [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" @@ -566,11 +555,7 @@ Describe "Build-Module" { $Value | Should -Be "pre-release" } - try { - Build-Module -Version "1.2.3" -Prerelease "pre-release" - } catch { - throw - } + Build-Module -Version "1.2.3" -Prerelease "pre-release" Assert-MockCalled Update-Metadata -ParameterFilter { $PropertyName -eq "PrivateData.PSData.Prerelease" -and $Value -eq "pre-release" @@ -588,11 +573,7 @@ Describe "Build-Module" { $Value | Should -Be "pre-release" } - try { - Build-Module -SemVer "1.2.3-pre-release" - } catch { - throw - } + Build-Module -SemVer "1.2.3-pre-release" Assert-MockCalled Update-Metadata -ParameterFilter { $PropertyName -eq "PrivateData.PSData.Prerelease" -and $Value -eq "pre-release" diff --git a/Tests/Public/ConvertFrom-SourceLineNumber.Tests.ps1 b/Tests/Public/ConvertFrom-SourceLineNumber.Tests.ps1 index 75fa522..8a75cec 100644 --- a/Tests/Public/ConvertFrom-SourceLineNumber.Tests.ps1 +++ b/Tests/Public/ConvertFrom-SourceLineNumber.Tests.ps1 @@ -1,29 +1,44 @@ -#requires -Module ModuleBuilder Describe "ConvertFrom-SourceLineNumber" { # use the integration test code - BeforeAll { + BeforeDiscovery { Build-Module $PSScriptRoot/../Integration/Source1/build.psd1 -Passthru Push-Location $PSScriptRoot -StackName ConvertFrom-SourceLineNumber - $global:Convert_LineNumber_ModulePath = Convert-Path "$PSScriptRoot/../Integration/Result1/Source1/1.0.0/Source1.psm1" - $global:Convert_LineNumber_ModuleSource = Convert-Path "$PSScriptRoot/../Integration/Source1" - $global:Convert_LineNumber_ModuleContent = Get-Content $global:Convert_LineNumber_ModulePath + $Convert_LineNumber_ModulePath = Convert-Path "$PSScriptRoot/../Integration/Result1/Source1/1.0.0/Source1.psm1" ${global:\} = [io.path]::DirectorySeparatorChar - $global:TestCases = @( - @{ outputLine = 40; sourceFile = ".${\}Private${\}TestUnExportedAliases.ps1"; sourceLine = 13; Module = $Convert_LineNumber_ModulePath } - @{ outputLine = 48; sourceFile = ".${\}Public${\}Get-Source.ps1"; sourceLine = 5; Module = $Convert_LineNumber_ModulePath } - @{ outputLine = 56; sourceFile = ".${\}Public${\}Set-Source.ps1"; sourceLine = 3; Module = $Convert_LineNumber_ModulePath } + $TestCases = @( + @{ + outputLine = 40 + sourceFile = ".${\}Private${\}TestUnExportedAliases.ps1" + sourceLine = 13 + module = $Convert_LineNumber_ModulePath + } + @{ + outputLine = 48 + sourceFile = ".${\}Public${\}Get-Source.ps1" + sourceLine = 5 + module = $Convert_LineNumber_ModulePath + } + @{ + outputLine = 56 + sourceFile = ".${\}Public${\}Set-Source.ps1" + sourceLine = 3 + module = $Convert_LineNumber_ModulePath + } ) } + BeforeAll { + $Convert_LineNumber_ModuleContent = Get-Content "$PSScriptRoot/../Integration/Result1/Source1/1.0.0/Source1.psm1" + } AfterAll { Pop-Location -StackName ConvertFrom-SourceLineNumber } - It "Should map line in the source of to line in the Module" -TestCases $TestCases { + It "Should map line in the source of to line in the Module " -TestCases $TestCases { param($outputLine, $sourceFile, $sourceLine, $module) - $sourcePath = Join-Path $Convert_LineNumber_ModuleSource $SourceFile | Convert-Path + $sourcePath = Join-Path "$PSScriptRoot/../Integration/Source1" $sourceFile | Convert-Path $OutputLocation = ConvertFrom-SourceLineNumber $sourcePath $sourceLine -Module $module # $OutputLocation.Script | Should -Be $Convert_LineNumber_ModulePath $OutputLocation.Line | Should -Be $outputLine @@ -37,6 +52,8 @@ Describe "ConvertFrom-SourceLineNumber" { } It "Should warn if the SourceFile doesn't exist" { + $Convert_LineNumber_ModulePath = Convert-Path "$PSScriptRoot/../Integration/Result1/Source1/1.0.0/Source1.psm1" + ConvertFrom-SourceLineNumber -SourceFile TestDrive:/NoSuchFile -SourceLineNumber 10 -Module $Convert_LineNumber_ModulePath -WarningVariable Warns $Warns | Should -Be "'TestDrive:/NoSuchFile' not found in $Convert_LineNumber_ModulePath" } diff --git a/Tests/Public/ConvertTo-SourceLineNumber.Tests.ps1 b/Tests/Public/ConvertTo-SourceLineNumber.Tests.ps1 index 0b423a9..c300c0a 100644 --- a/Tests/Public/ConvertTo-SourceLineNumber.Tests.ps1 +++ b/Tests/Public/ConvertTo-SourceLineNumber.Tests.ps1 @@ -1,21 +1,33 @@ -#requires -Module ModuleBuilder Describe "ConvertTo-SourceLineNumber" { # use the integration test code - BeforeAll { + BeforeDiscovery { Build-Module $PSScriptRoot/../Integration/Source1/build.psd1 -Passthru Push-Location $PSScriptRoot -StackName ConvertTo-SourceLineNumber - $global:Convert_LineNumber_ModulePath = Convert-Path "./../Integration/Result1/Source1/1.0.0/Source1.psm1" - $global:Convert_LineNumber_ModuleSource = Convert-Path "./../Integration/Source1" - $global:Convert_LineNumber_ModuleContent = Get-Content $global:Convert_LineNumber_ModulePath ${global:\} = [io.path]::DirectorySeparatorChar - $global:TestCases = @( - @{ outputLine = 40; sourceFile = ".${\}Private${\}TestUnExportedAliases.ps1"; sourceLine = 13 } - @{ outputLine = 48; sourceFile = ".${\}Public${\}Get-Source.ps1"; sourceLine = 5 } - @{ outputLine = 56; sourceFile = ".${\}Public${\}Set-Source.ps1"; sourceLine = 3 } + $TestCases = @( + @{ + outputLine = 40 + sourceFile = ".${\}Private${\}TestUnExportedAliases.ps1" + sourceLine = 13 + } + @{ + outputLine = 48 + sourceFile = ".${\}Public${\}Get-Source.ps1" + sourceLine = 5 + } + @{ + outputLine = 56 + sourceFile = ".${\}Public${\}Set-Source.ps1" + sourceLine = 3 + } ) } + BeforeAll { + $Convert_LineNumber_ModulePath = Convert-Path "$PSScriptRoot/../Integration/Result1/Source1/1.0.0/Source1.psm1" + $Convert_LineNumber_ModuleContent = Get-Content "$PSScriptRoot/../Integration/Result1/Source1/1.0.0/Source1.psm1" + } AfterAll { Pop-Location -StackName ConvertTo-SourceLineNumber } @@ -28,7 +40,7 @@ Describe "ConvertTo-SourceLineNumber" { $SourceLocation.SourceFile | Should -Be $SourceFile $SourceLocation.SourceLineNumber | Should -Be $SourceLine - $line = (Get-Content (Join-Path $Convert_LineNumber_ModuleSource $SourceLocation.SourceFile))[$SourceLocation.SourceLineNumber - 1] + $line = (Get-Content (Convert-Path (Join-Path "$PSScriptRoot/../Integration/Source1" $SourceLocation.SourceFile)))[$SourceLocation.SourceLineNumber - 1] try { $Convert_LineNumber_ModuleContent[$outputLine -1] | Should -Be $line } catch { @@ -52,7 +64,7 @@ Describe "ConvertTo-SourceLineNumber" { It 'Should work with ScriptStackTrace messages' { - $SourceFile = Join-Path $Convert_LineNumber_ModuleSource Public/Set-Source.ps1 | Convert-Path + $SourceFile = Join-Path "$PSScriptRoot/../Integration/Source1" Public/Set-Source.ps1 | Convert-Path $outputLine = Select-String -Path $Convert_LineNumber_ModulePath "sto͞o′pĭd" | % LineNumber $sourceLine = Select-String -Path $SourceFile "sto͞o′pĭd" | % LineNumber @@ -70,7 +82,7 @@ Describe "ConvertTo-SourceLineNumber" { Function = 'Get-Source' # these are pipeline bound File = $Convert_LineNumber_ModulePath - Line = 48 # 1 offset with the Using Statement introduced in MoveUsingStatements + Line = 48 # 1 offset with the Using Statement introduced in MoveUsingStatement } $SourceLocation = $PesterMiss | Convert-LineNumber -Passthru diff --git a/Tests/Private/MoveUsingStatements.Tests.ps1 b/Tests/Public/Move-UsingStatement.Tests.ps1.old similarity index 85% rename from Tests/Private/MoveUsingStatements.Tests.ps1 rename to Tests/Public/Move-UsingStatement.Tests.ps1.old index e2f8f8e..6f3326c 100644 --- a/Tests/Private/MoveUsingStatements.Tests.ps1 +++ b/Tests/Public/Move-UsingStatement.Tests.ps1.old @@ -1,29 +1,21 @@ -#requires -Module ModuleBuilder -Describe "MoveUsingStatements" { +Describe "Move-UsingStatement" { Context "Necessary Parameters" { - $CommandInfo = InModuleScope ModuleBuilder { Get-Command MoveUsingStatements } + $CommandInfo = InModuleScope ModuleBuilder { Get-Command MoveUsingStatement } It 'has a mandatory AST parameter' { - $AST = $CommandInfo.Parameters['AST'] + $AST = $CommandInfo.Parameters['InputObject'] $AST | Should -Not -BeNullOrEmpty $AST.ParameterType | Should -Be ([System.Management.Automation.Language.Ast]) $AST.Attributes.Where{ $_ -is [Parameter] }.Mandatory | Should -Be $true } - - It "has an optional string Encoding parameter" { - $Encoding = $CommandInfo.Parameters['Encoding'] - $Encoding | Should -Not -BeNullOrEmpty - $Encoding.ParameterType | Should -Be ([String]) - $Encoding.Attributes.Where{$_ -is [Parameter]}.Mandatory | Should -Be $False - } } Context "Moving Using Statements to the beginning of the file" { - $MoveUsingStatementsCmd = InModuleScope ModuleBuilder { + $MoveUsingStatementCmd = InModuleScope ModuleBuilder { $null = Mock Write-Warning { } { param($RootModule) - ConvertToAst $RootModule | MoveUsingStatements + ConvertToAst $RootModule | MoveUsingStatement } } @@ -107,7 +99,7 @@ Describe "MoveUsingStatements" { $ErrorFound.Count | Should -Be $ErrorBefore # After - &$MoveUsingStatementsCmd -RootModule $testModuleFile + &$MoveUsingStatementCmd -RootModule $testModuleFile $null = [System.Management.Automation.Language.Parser]::ParseFile( $testModuleFile, @@ -122,15 +114,15 @@ Describe "MoveUsingStatements" { } } - Context "When MoveUsingStatements should do nothing" { + Context "When MoveUsingStatement should do nothing" { - $MoveUsingStatementsCmd = InModuleScope ModuleBuilder { + $MoveUsingStatementCmd = InModuleScope ModuleBuilder { $null = Mock Write-Warning {} $null = Mock Set-Content {} $null = Mock Write-Debug {} -ParameterFilter { $Message -eq "No using statement errors found." } { param($RootModule) - ConvertToAst $RootModule | MoveUsingStatements + ConvertToAst $RootModule | MoveUsingStatement } } @@ -139,7 +131,7 @@ Describe "MoveUsingStatements" { $PSM1File = "using namespace System.IO; function x {}" Set-Content $testModuleFile -value $PSM1File -Encoding UTF8 - &$MoveUsingStatementsCmd -RootModule $testModuleFile -Debug + &$MoveUsingStatementCmd -RootModule $testModuleFile -Debug (Get-Content -Raw $testModuleFile).Trim() | Should -Be $PSM1File diff --git a/Tests/Private/GetCommandAlias.Tests.ps1 b/Tests/Public/Update-AliasesToExport.Tests.ps1.old similarity index 96% rename from Tests/Private/GetCommandAlias.Tests.ps1 rename to Tests/Public/Update-AliasesToExport.Tests.ps1.old index 63e899d..3bbb77e 100644 --- a/Tests/Private/GetCommandAlias.Tests.ps1 +++ b/Tests/Public/Update-AliasesToExport.Tests.ps1.old @@ -1,180 +1,179 @@ -#requires -Module ModuleBuilder -Describe "GetCommandAlias" { - BeforeAll { - $CommandInfo = InModuleScope ModuleBuilder { Get-Command GetCommandAlias } - } - - Context "Mandatory Parameter" { - It 'has a mandatory AST parameter' { - $AST = $CommandInfo.Parameters['AST'] - $AST | Should -Not -BeNullOrEmpty - $AST.ParameterType | Should -Be ([System.Management.Automation.Language.Ast]) - $AST.Attributes.Where{ $_ -is [Parameter] }.Mandatory | Should -Be $true - } - - } - - Context "Parsing Alias Parameters" { - # It used to return a hashtable, but we no longer care what the alias points to - It "Returns a collection of aliases" { - $Result = &$CommandInfo -Ast { - function Test-Alias { - [Alias("Foo","Bar","Alias")] - param() - } - }.Ast - - $Result | Should -Be @("Foo", "Bar", "Alias") - } - - It "Parses only top-level functions, and returns them in order" { - $Result = &$CommandInfo -Ast { - function Test-Alias { - [Alias("TA", "TAlias")] - param() - } - - function TestAlias { - [Alias("T")] - param() - - # This should not return - function Test-Negative { - [Alias("TN")] - param() - } - } - }.Ast - - $Result | Should -Be "TA","TAlias", "T" - } - } - - Context "Parsing New-Alias" { - It "Parses alias names regardless of parameter order" { - $Result = &$CommandInfo -Ast { - New-Alias -N 'Alias1' -Va 'Write-Verbose' - New-Alias -Value 'Write-Verbose' -Name 'Alias2' - New-Alias Alias3 Write-Verbose - New-Alias -Value 'Write-Verbose' 'Alias4' - New-Alias 'Alias5' -Value 'Write-Verbose' - }.Ast - - $Result | Should -Be 'Alias1', 'Alias2', 'Alias3', 'Alias4', 'Alias5' - } - - It "Ignores aliases defined in nested function scope" { - $Result = &$CommandInfo -Ast { - New-Alias -Name 'Alias1' -Value 'Write-Verbose' - New-Alias -Value 'Write-Verbose' -Name 'Alias2' - New-Alias 'Alias3' 'Write-Verbose' - function Get-Something { - param() - - New-Alias -Name Alias4 -Value 'Write-Verbose' - New-Alias -Name Alias5 -Va 'Write-Verbose' - } - }.Ast - - $Result | Should -Be 'Alias1', 'Alias2', 'Alias3' - } - - It "Ignores aliases that already have global scope" { - $Result = &$CommandInfo -Ast { - New-Alias -Name Alias1 -Scope Global -Value Write-Verbose - New-Alias -Scope Global -Value 'Write-Verbose' -Name 'Alias2' - New-Alias -Sc Global 'Alias3' 'Write-Verbose' - New-Alias -Va 'Write-Verbose' 'Alias4' -S Global - New-Alias Alias5 -Value 'Write-Verbose' -Scope Global - }.Ast - - $Result | Should -BeNullOrEmpty - } - } - - Context "Parsing Set-Alias" { - It "Parses alias names regardless of parameter order" { - $Result = &$CommandInfo -Ast { - Set-Alias -Name Alias1 -Value Write-Verbose - Set-Alias -Va 'Write-Verbose' -N 'Alias2' - Set-Alias Alias3 Write-Verbose - Set-Alias -Va 'Write-Verbose' 'Alias4' - Set-Alias 'Alias5' -Value 'Write-Verbose' - }.Ast - - $Result | Should -Be 'Alias1', 'Alias2', 'Alias3', 'Alias4', 'Alias5' - } - - It "Ignores aliases defined in nested function scope" { - $Result = &$CommandInfo -Ast { - Set-Alias -Name 'Alias1' -Value 'Write-Verbose' - Set-Alias -Value 'Write-Verbose' -Name 'Alias2' - Set-Alias 'Alias3' 'Write-Verbose' - function Get-Something { - param() - - Set-Alias -Name 'Alias4' -Value 'Write-Verbose' - Set-Alias -Name 'Alias5' -Value 'Write-Verbose' - } - }.Ast - - $Result | Should -Be 'Alias1', 'Alias2', 'Alias3' - } - - It "Ignores aliases that already have global scope" { - $Result = &$CommandInfo -Ast { - Set-Alias -N 'Alias1' -Scope Global -Value 'Write-Verbose' - Set-Alias -Scope Global -Value 'Write-Verbose' -Name 'Alias2' - Set-Alias -Sc Global Alias3 Write-Verbose - Set-Alias -Va 'Write-Verbose' 'Alias4' -Sc Global - Set-Alias 'Alias5' -Value 'Write-Verbose' -Scope Global - }.Ast - - $Result | Should -BeNullOrEmpty - } - } - - - Context "Remove-Alias cancels Alias exports" { - It "Parses parameters regardless of name" { - $Result = &$CommandInfo -Ast { - New-Alias -Name Alias1 -Value Write-Verbose - Set-Alias -Value 'Write-Verbose' -Name 'Alias2' - New-Alias Alias3 Write-Verbose - Set-Alias -Value 'Write-Verbose' 'Alias4' - Set-Alias 'Alias5' -Value 'Write-Verbose' - Remove-Alias Alias1 - Remove-Alias -Name Alias2 - Remove-Alias -N Alias5 - }.Ast - - $Result | Should -Be 'Alias3', 'Alias4' - } - - It "Ignores removals in function scopes" { - $Result = &$CommandInfo -Ast { - Set-Alias -Name 'Alias1' -Value 'Write-Verbose' - New-Alias -Value 'Write-Verbose' -Name 'Alias2' - Set-Alias 'Alias3' 'Write-Verbose' - function Get-Something { - param() - - Set-Alias -Name 'Alias4' -Value 'Write-Verbose' - Set-Alias -Name 'Alias5' -Value 'Write-Verbose' - Remove-Alias -Name Alias1 - } - }.Ast - - $Result | Should -Be 'Alias1', 'Alias2', 'Alias3' - } - - It "Does not fail when removing aliases that were ignored because of global scope" { - $Result = &$CommandInfo -Ast { - Set-Alias -Name Alias1 -Scope Global -Value Write-Verbose - Remove-Alias -Name Alias1 - }.Ast - - $Result | Should -BeNullOrEmpty - } - } -} +Describe "GetCommandAlias" { + BeforeAll { + $CommandInfo = InModuleScope ModuleBuilder { Get-Command GetCommandAlias } + } + + Context "Mandatory Parameter" { + It 'has a mandatory AST parameter' { + $AST = $CommandInfo.Parameters['AST'] + $AST | Should -Not -BeNullOrEmpty + $AST.ParameterType | Should -Be ([System.Management.Automation.Language.Ast]) + $AST.Attributes.Where{ $_ -is [Parameter] }.Mandatory | Should -Be $true + } + + } + + Context "Parsing Alias Parameters" { + # It used to return a hashtable, but we no longer care what the alias points to + It "Returns a collection of aliases" { + $Result = &$CommandInfo -Ast { + function Test-Alias { + [Alias("Foo","Bar","Alias")] + param() + } + }.Ast + + $Result | Should -Be @("Foo", "Bar", "Alias") + } + + It "Parses only top-level functions, and returns them in order" { + $Result = &$CommandInfo -Ast { + function Test-Alias { + [Alias("TA", "TAlias")] + param() + } + + function TestAlias { + [Alias("T")] + param() + + # This should not return + function Test-Negative { + [Alias("TN")] + param() + } + } + }.Ast + + $Result | Should -Be "TA","TAlias", "T" + } + } + + Context "Parsing New-Alias" { + It "Parses alias names regardless of parameter order" { + $Result = &$CommandInfo -Ast { + New-Alias -N 'Alias1' -Va 'Write-Verbose' + New-Alias -Value 'Write-Verbose' -Name 'Alias2' + New-Alias Alias3 Write-Verbose + New-Alias -Value 'Write-Verbose' 'Alias4' + New-Alias 'Alias5' -Value 'Write-Verbose' + }.Ast + + $Result | Should -Be 'Alias1', 'Alias2', 'Alias3', 'Alias4', 'Alias5' + } + + It "Ignores aliases defined in nested function scope" { + $Result = &$CommandInfo -Ast { + New-Alias -Name 'Alias1' -Value 'Write-Verbose' + New-Alias -Value 'Write-Verbose' -Name 'Alias2' + New-Alias 'Alias3' 'Write-Verbose' + function Get-Something { + param() + + New-Alias -Name Alias4 -Value 'Write-Verbose' + New-Alias -Name Alias5 -Va 'Write-Verbose' + } + }.Ast + + $Result | Should -Be 'Alias1', 'Alias2', 'Alias3' + } + + It "Ignores aliases that already have global scope" { + $Result = &$CommandInfo -Ast { + New-Alias -Name Alias1 -Scope Global -Value Write-Verbose + New-Alias -Scope Global -Value 'Write-Verbose' -Name 'Alias2' + New-Alias -Sc Global 'Alias3' 'Write-Verbose' + New-Alias -Va 'Write-Verbose' 'Alias4' -S Global + New-Alias Alias5 -Value 'Write-Verbose' -Scope Global + }.Ast + + $Result | Should -BeNullOrEmpty + } + } + + Context "Parsing Set-Alias" { + It "Parses alias names regardless of parameter order" { + $Result = &$CommandInfo -Ast { + Set-Alias -Name Alias1 -Value Write-Verbose + Set-Alias -Va 'Write-Verbose' -N 'Alias2' + Set-Alias Alias3 Write-Verbose + Set-Alias -Va 'Write-Verbose' 'Alias4' + Set-Alias 'Alias5' -Value 'Write-Verbose' + }.Ast + + $Result | Should -Be 'Alias1', 'Alias2', 'Alias3', 'Alias4', 'Alias5' + } + + It "Ignores aliases defined in nested function scope" { + $Result = &$CommandInfo -Ast { + Set-Alias -Name 'Alias1' -Value 'Write-Verbose' + Set-Alias -Value 'Write-Verbose' -Name 'Alias2' + Set-Alias 'Alias3' 'Write-Verbose' + function Get-Something { + param() + + Set-Alias -Name 'Alias4' -Value 'Write-Verbose' + Set-Alias -Name 'Alias5' -Value 'Write-Verbose' + } + }.Ast + + $Result | Should -Be 'Alias1', 'Alias2', 'Alias3' + } + + It "Ignores aliases that already have global scope" { + $Result = &$CommandInfo -Ast { + Set-Alias -N 'Alias1' -Scope Global -Value 'Write-Verbose' + Set-Alias -Scope Global -Value 'Write-Verbose' -Name 'Alias2' + Set-Alias -Sc Global Alias3 Write-Verbose + Set-Alias -Va 'Write-Verbose' 'Alias4' -Sc Global + Set-Alias 'Alias5' -Value 'Write-Verbose' -Scope Global + }.Ast + + $Result | Should -BeNullOrEmpty + } + } + + + Context "Remove-Alias cancels Alias exports" { + It "Parses parameters regardless of name" { + $Result = &$CommandInfo -Ast { + New-Alias -Name Alias1 -Value Write-Verbose + Set-Alias -Value 'Write-Verbose' -Name 'Alias2' + New-Alias Alias3 Write-Verbose + Set-Alias -Value 'Write-Verbose' 'Alias4' + Set-Alias 'Alias5' -Value 'Write-Verbose' + Remove-Alias Alias1 + Remove-Alias -Name Alias2 + Remove-Alias -N Alias5 + }.Ast + + $Result | Should -Be 'Alias3', 'Alias4' + } + + It "Ignores removals in function scopes" { + $Result = &$CommandInfo -Ast { + Set-Alias -Name 'Alias1' -Value 'Write-Verbose' + New-Alias -Value 'Write-Verbose' -Name 'Alias2' + Set-Alias 'Alias3' 'Write-Verbose' + function Get-Something { + param() + + Set-Alias -Name 'Alias4' -Value 'Write-Verbose' + Set-Alias -Name 'Alias5' -Value 'Write-Verbose' + Remove-Alias -Name Alias1 + } + }.Ast + + $Result | Should -Be 'Alias1', 'Alias2', 'Alias3' + } + + It "Does not fail when removing aliases that were ignored because of global scope" { + $Result = &$CommandInfo -Ast { + Set-Alias -Name Alias1 -Scope Global -Value Write-Verbose + Remove-Alias -Name Alias1 + }.Ast + + $Result | Should -BeNullOrEmpty + } + } +}