diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 94cb4da..3eb5114 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,21 +42,18 @@ jobs: name: TestResults path: Modules/ModuleBuilder-TestResults - - uses: actions/upload-artifact@v4 - with: - name: Packages - path: Modules/ModuleBuilder-Packages - + # These ones are just for the test matrix - name: Upload Tests uses: actions/upload-artifact@v4 with: name: PesterTests path: ${{github.workspace}}/Tests - - name: Upload RequiredModules.psd1 + + - name: Upload build.requires.psd1 uses: actions/upload-artifact@v4 with: - name: RequiredModules - path: ${{github.workspace}}/RequiredModules.psd1 + name: build.requires.psd1 + path: ${{github.workspace}}/build.requires.psd1 test: needs: build runs-on: ${{ matrix.os }} @@ -69,29 +66,43 @@ jobs: uses: actions/download-artifact@v4 with: name: ModuleBuilder - path: Modules/ModuleBuilder + path: Modules/ModuleBuilder # /home/runner/work/ModuleBuilder/ModuleBuilder/Modules/ModuleBuilder - name: Download Pester Tests uses: actions/download-artifact@v4 with: name: PesterTests path: PesterTests - - name: Download RequiredModules + - name: Download build.requires.psd1 uses: actions/download-artifact@v4 with: - name: RequiredModules - - - uses: PoshCode/Actions/install-requiredmodules@v1 - - uses: PoshCode/Actions/pester@v1 - with: - codeCoveragePath: Modules/ModuleBuilder - moduleUnderTest: ModuleBuilder - additionalModulePaths: ${{github.workspace}}/Modules - - name: Publish Test Results - uses: zyborg/dotnet-tests-report@v1 + name: build.requires.psd1 + - name: ⚡ Install PowerShell Modules + uses: JustinGrote/ModuleFast-action@v0.0.1 + - name: Put Build output in PATH + shell: pwsh + run: | # PowerShell + Convert-Path Modules -OutVariable BuiltModules + Add-Content -Path $env:GITHUB_PATH -Value $BuiltModules -Encoding utf8 + # Uninstall the "installed" copy of ModuleBuilder + Get-Module -Name ModuleBuilder -List | Where ModuleBase -notmatch ([regex]::escape($pwd)) | Split-Path | Remove-Item -Recurse -Force + - name: Put Build output in PATH + shell: pwsh + run: | # PowerShell + $Env:PATH -split ([IO.Path]::PathSeparator) | Out-Host + - name: Invoke Pester Tests + id: pester + uses: zyborg/pester-tests-report@v1 with: - test_results_path: results.xml - - name: Upload Results - uses: actions/upload-artifact@v2 - with: - name: Pester Results - path: ${{github.workspace}}/*.xml + # include_paths: tests + # exclude_paths: tests/powershell1,tests/powershell2 + # exclude_tags: skip_ci + report_name: module_tests + report_title: My Module Tests + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: dump test results + shell: pwsh + run: | # PowerShell + Write-Host 'Total Tests Executed...: ${{ steps.pester.outputs.total_count }}' + Write-Host 'Total Tests PASSED.....: ${{ steps.pester.outputs.passed_count }}' + Write-Host 'Total Tests FAILED.....: ${{ steps.pester.outputs.failed_count }}' + diff --git a/.github/workflows/dotnet-tools.json b/.github/workflows/dotnet-tools.json new file mode 100644 index 0000000..c11b918 --- /dev/null +++ b/.github/workflows/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "gitversion.tool": { + "version": "5.6.0", + "commands": [ + "dotnet-gitversion" + ] + } + } +} diff --git a/Build.build.ps1 b/Build.build.ps1 index 2ecfbdf..6b5738d 100644 --- a/Build.build.ps1 +++ b/Build.build.ps1 @@ -16,8 +16,8 @@ param( # Add the clean task before the default build [switch]$Clean, - # Collect code coverage when tests are run - [switch]$CollectCoverage, + # A minimum code coverage percentage to accept as a double: 0.85 + [double]$RequiredCodeCoverage = 0.85, # Which projects to build [Alias("Projects")] diff --git a/Earthfile b/Earthfile index be47ddf..bea1238 100644 --- a/Earthfile +++ b/Earthfile @@ -1,6 +1,6 @@ VERSION 0.7 IMPORT github.com/poshcode/tasks -FROM mcr.microsoft.com/dotnet/sdk:7.0 +FROM mcr.microsoft.com/dotnet/sdk:9.0 WORKDIR /work ARG --global EARTHLY_GIT_ORIGIN_URL @@ -25,9 +25,9 @@ worker: COPY tasks+tasks/* /Tasks # Dealing with dependencies first allows docker to cache packages for us # So the dependency cach only re-builds when you add a new dependency - COPY RequiredModules.psd1 . + COPY build.requires.psd1 . # COPY *.csproj . - RUN ["pwsh", "-File", "/Tasks/_Bootstrap.ps1", "-RequiredModulesPath", "RequiredModules.psd1"] + RUN ["pwsh", "-File", "/Tasks/_Bootstrap.ps1", "-RequiresPath", "build.requires.psd1"] build: FROM +worker diff --git a/GitVersion.yml b/GitVersion.yml index c9554fa..882cb9f 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,19 +1,46 @@ -mode: Mainline -assembly-versioning-format: '{Major}.{Minor}.{Patch}.{env:BUILDCOUNT ?? 0}' -assembly-informational-format: '{NuGetVersionV2}+Build.{env:BUILDCOUNT ?? 0}.Date.{CommitDate}.Branch.{env:SAFEBRANCHNAME ?? unknown}.Sha.{Sha}' -major-version-bump-message: '\+?semver:\s?(breaking|major)' -minor-version-bump-message: '\+?semver:\s?(feature|minor)' -patch-version-bump-message: '\+?semver:\s?(fix|patch)' -no-bump-message: '\+?semver:\s?(none|skip)' +mode: mainline +commit-date-format: "yyyyMMddTHHmmss" +assembly-file-versioning-format: "{Major}.{Minor}.{Patch}.{env:GITHUB_RUN_NUMBER ?? 0}" + +# This repo needs to use NuGetVersionV2 for compatibility with PowerShellGallery +assembly-informational-format: "{NuGetVersionV2}+Build.{env:GITHUB_RUN_NUMBER ?? local}.Branch.{EscapedBranchName}.Sha.{Sha}.Date.{CommitDate}" +major-version-bump-message: 'semver:\s?(breaking|major)' +minor-version-bump-message: 'semver:\s?(feature|minor)' +patch-version-bump-message: 'semver:\s?(fix|patch)' +no-bump-message: 'semver:\s?(none|skip)' +commit-message-incrementing: Enabled + branches: - master: + main: + tag: "" # explicitly no tag for main builds + regex: ^main$ increment: Patch - pull-request: + is-mainline: true + tracks-release-branches: true + hotfix: + tag: rc + regex: hotfix(es)?/\d+\.\d+\.\d+ + increment: None + is-release-branch: true + prevent-increment-of-merged-branch-version: true + source-branches: [ "main" ] + release: tag: rc + regex: releases?/\d+\.\d+\.\d+ + increment: None + is-release-branch: true + prevent-increment-of-merged-branch-version: true + source-branches: [ "main" ] + pull-request: + regex: pull/ + tag: pr + tag-number-pattern: '[/-](?\d+)' increment: Patch + source-branches: [ "main", "feature", "release", "hotfix" ] feature: + regex: .*/ + tag: useBranchName + source-branches: [ "main", "feature" ] + track-merge-target: true + tracks-release-branches: true increment: Patch - regex: .*?/ - source-branches: - - master - - feature diff --git a/ReadMe.md b/ReadMe.md index 0bf11ce..156d7bf 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,66 +1,59 @@ -# The Module Builder Project +# Module Builder - Simplifying Authoring PowerShell (Script) Modules -This project is an attempt by a group of PowerShell MVPs and module authors to: +This module makes it easier to break up your module source into several files for organization, even though you need to ship it as one big psm1 file. -1. Build a common set of [tools for module authoring](#whats-in-the-module-so-far) -2. Encourage a common pattern for [organizing PowerShell module projects](#organizing-your-module) -3. Promote best practices for authoring functions and modules +There are still some issues in Visual Studio Code and PSScriptAnalyzer when authoring modules as multiple files, but if you want to break up your module into multiple files for organization and maintainability, and still need to ship it as one big file for performance and compatibility reasons, this module is for you. -In short, we want to make it easier for people to write great code and produce great modules. +## You should ship your module as one big file! -In service of this goal, we intend to produce: +PowerShell expects script modules to be all in one file. A module in a single `.psm1` script file results in the best performance, natural "script" scope, and full support for classes and "using" statements. -1. Guidance on using the best of the existing tools: dotnet, Pester, PSDepends, etc. -2. Module templates demonstrating best practices for organization -3. Function templates demonstrating best practices for common parameters and error handling -4. ModuleBuilder module - a set of tools for building modules following these best practices +The single file option is particularly important for performance if you are signing your module (or your end users want to be able to code-sign it), because each file's signature must be checked, and each certificate must be checked against CRLs. It's also critical if you are using PowerShell classes (the `using` statement only supports classes defined in the root psm1 file). It's basically required if you want to use module-scope variables to share state between functions in your module. -## The ModuleBuilder module +## What's in the ModuleBuilder module so far? -This module is the main output of the project, consisting of one primary command: `Build-Module` and a few helpers to translate input and output line numbers. It represents the collaboration of several module authors who had each written their own version of these tools for themselves, and have now decided to collaborate on creating a shared tool set. We are each using the patterns and tools that are represented here, and are committed to helping others to succeed at doing so. +This module is the main output of the project, consisting of one primary command: `Build-Module` and a few helpers to translate input and output line numbers so you can trouble-shoot error messages from your module against the source files. -### What's in the module so far - -#### Build-Module +### Build-Module Builds a script module from a source project containing one file per function in `Public` and `Private` folders. The `Build-Module` command is a build task for PowerShell script modules that supports [incremental builds](https://docs.microsoft.com/en-us/visualstudio/msbuild/incremental-builds). -#### Convert-CodeCoverage +### Convert-CodeCoverage Takes the output from `Invoke-Pester -Passthru` run against the build output, and converts the code coverage report to the source lines. -#### ConvertFrom-SourceLineNumber +### ConvertFrom-SourceLineNumber Converts a line number from a source file to the corresponding line number in the built output. -#### ConvertTo-SourceLineNumber +### ConvertTo-SourceLineNumber Converts a line number from the built output to the corresponding file and line number in the source. -#### Convert-Breakpoint +### Convert-Breakpoint Convert any breakpoints on source files to module files _and vice-versa_. ## Organizing Your Module -For best results, you need to organize your module project similarly to how this project is organized. It doesn't have to be exact, because nearly all of our conventions can be overriden, but the module _is_ opinionated, so if you follow the conventions, it should feel wonderfully automatic. +For best results, you need to organize your module project similarly to how this project is organized. It doesn't have to be exact, because you can override nearly all of our conventions, but the module _is_ opinionated, so if you follow the conventions, it should feel wonderfully automatic. -1. Create a `source` folder with a `build.psd1` file and your module manifest in it +1. Create a `source` (or `src`) folder with a `build.psd1` file and your module manifest in it 2. In the `build.psd1` specify the relative **Path** to your module's manifest, e.g. `@{ Path = "ModuleBuilder.psd1" }` 3. In your manifest, make sure a few values are not commented out. You can leave them empty, because they'll be overwritten: - `FunctionsToExport` will be updated with the _file names_ that match the `PublicFilter` - `AliasesToExport` will be updated with the values from `[Alias()]` attributes on commands - `Prerelease` and `ReleaseNotes` in the `PSData` hash table in `PrivateData` -Once you start working on the module, you'll create sub-folders in source, and put script files in them with only **one** function in each file. You should name the files with _the same name_ as the function that's in them -- especially in the public folder, where we use the file name (without the extension) to determine the exported functions. +Once you start working on the module, you'll create sub-folders in source, and put script files in them with only **one** function in each file. You should name the files with _the same name_ as the function that's in them -- especially in the `source\public` folder, where we use the file names to determine the exported functions. -1. By convention, use folders named "Classes" (and/or "Enum"), "Private", and "Public" -2. By convention, the functions in "Public" will be exported from the module (you can override the `PublicFilter`) +1. By convention, use SourceDirectories named "Classes" (and/or "Enum"), "Private", and "Public" +2. By convention, the PublicFilter is all of the functions in the "Public" directory. 3. To force classes to be in a certain order, you can prefix their file names with numbers, like `01-User.ps1` -There are a _lot_ of conventions in `Build-Module`, expressed as default values for its parameters. These defaults are documented in the help for Build-Module. You can override any parameter defaults by adding keys to the `build.psd1` file with your preferences, or by passing the values to the `Build-Module` command directly. +There are a _lot_ of conventions in `Build-Module`, expressed as default values for its parameters. These defaults are documented in the help for Build-Module, and you can override any parameter defaults by adding keys to the `build.psd1` file with your preferences, or by passing the values to the `Build-Module` command directly. So in other words, you can override the default `SourceDirectories` and `PublicFilters` (and any others) by adding them to the `build.psd1` file. ## A note on build tools @@ -93,7 +86,7 @@ git clone https://github.com/PoshCode/ModuleBuilder.git git clone https://github.com/PoshCode/Tasks.git ``` -Once you've cloned both, the `Build.build.ps1` script will use the shared [Tasks\_Bootstrap.ps1](https://github.com/PoshCode/Tasks/blob/main/_Bootstrap.ps1) to install the other dependencies (see [RequiredModules.psd1](https://github.com/PoshCode/ModuleBuilder/blob/main/RequiredModules.psd1)), including [dotnet](https://dot.net), and will use [Invoke-Build](https://github.com/nightroman/Invoke-Build) and [Pester](https://github.com/Pester/Pester) to build and test the module. +Once you've cloned both, the `Build.build.ps1` script will use the shared [Tasks\_Bootstrap.ps1](https://github.com/PoshCode/Tasks/blob/main/_Bootstrap.ps1) to install the other dependencies (see [build.requires.psd1](https://github.com/PoshCode/ModuleBuilder/blob/main/build.requires.psd1)), including [dotnet](https://dot.net), and will use [Invoke-Build](https://github.com/nightroman/Invoke-Build) and [Pester](https://github.com/Pester/Pester) to build and test the module. ```powershell cd ModuleBuilder @@ -102,23 +95,18 @@ cd ModuleBuilder This _should_ work on Windows, Linux, or MacOS. I test the build process on Windows, and in CI we run it in the Linux containers via earthly, and we run the full Pester test suit on all three platforms. -#### The old-fashioned way +## Most recent releases -You _can_ build the module without any additional tools (and without running tests), by using the old `build.ps1` bootstrap script. You'll need to pass a version number in, and if you have [Pester](https://github.com/Pester/Pester) and [PSScriptAnalyzer](https://github.com/PowerShell/PSScriptAnalyzer), you can run the 'test.ps1' script to run the tests. +### 3.2.0 - Script Generators -```powershell -./build.ps1 -Semver 5.0.0-prerelease | Split-Path | Import-Module -Force -./test.ps1 -``` +Script Generators let developers modify their module's source code as it is being built. A generator can create new script functions on the fly, such that whole functions are added to the built module. A generator can also inject boilerplate code like error handling, logging, tracing and timing at build-time, so this code can be maintained once, and be automatically added (and updated) in all the places where it's needed when the module is built. The generators run during the build and can inspect existing functions, data files, or even data from an API, and produce code that is output into the module (and clearly marked as generated). + +### 3.1.0 - Supports help outside the top of script commands -## Changelog +Starting with this release, ModuleBuilder adds an empty line between the `#REGION filename` comment lines it injects, and the content of the files. This allows PowerShell to recognize help comments that are at the top of each file (outside the function block). -### 3.0.0 - Now with better alias support +### 3.0.0 - Better alias support Starting with this release, ModuleBuilder will automatically export aliases from `New-Alias` and `Set-Alias` as well as the `[Alias()]` attributes on commands. This is (probably not) a breaking change, but because it can change the aliases exported by existing modules that use ModuleBuilder, I've bumped the major version number as a precaution (if you're reading this, mission accomplished). Additionally, the `Build-Module` command now _explicitly sorts_ the source files into alphabetical order, to ensure consistent behavior regardless of the native order of the underlying file system. This is technically also a breaking change, but it's unlikely to affect anyone except the people whose builds didn't work on non-Windows systems because of the previous behavior. - -### 3.1.0 - Now allows help outside the top of script commands - -Starting with this release, ModuleBuilder adds an empty line between the `#REGION filename` comment lines it injects, and the content of the files. This allows PowerShell to recognize help comments that are at the top of each file (outside the function block). diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 deleted file mode 100644 index 109c593..0000000 --- a/RequiredModules.psd1 +++ /dev/null @@ -1,10 +0,0 @@ -# NOTE: follow nuget syntax for versions: https://docs.microsoft.com/en-us/nuget/reference/package-versioning#version-ranges-and-wildcards -@{ - Configuration = "[1.5.0, 2.0)" - Metadata = "[1.5.1, 2.0)" - Pester = "[4.10.1,5.0)" - ModuleBuilder = "[3.0.0, 4.0)" - PSScriptAnalyzer = "[1.21.0,2.0)" - PowerShellGet = "2.0.4" - InvokeBuild = "[5.10.4,6.0)" -} diff --git a/Tests/Integration/Parameters.Tests.ps1 b/Tests/Integration/Parameters.Tests.ps1 index 6206696..2cec9c9 100644 --- a/Tests/Integration/Parameters.Tests.ps1 +++ b/Tests/Integration/Parameters.Tests.ps1 @@ -1,29 +1,42 @@ #requires -Module ModuleBuilder -. $PSScriptRoot\..\Convert-FolderSeparator.ps1 -Describe "Parameters.Set in build manifest" -Tag Integration { +Describe "Parameters" -Tag Integration { BeforeAll { - 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") - $Metadata = Import-Metadata $Output.Path + . $PSScriptRoot/../Convert-FolderSeparator.ps1 + # Make sure the Result3 folder is really clean ;) + if (Test-Path $PSScriptRoot/Result3) { + Remove-Item $PSScriptRoot/Result3 -Recurse -Force } + # Throw in an extra file that would get cleaned up normally ... + New-Item $PSScriptRoot/Result3/Parameters/3.0.0/DeleteMe.md -ItemType File -Force + + Write-Host "Module Under Test:" + Get-Command Build-Module + | Get-Module -Name { $_.Source } + | Get-Item + | Out-Host } - It "Passthru works" { + It "Passthru is read from the build manifest" { + Build-Module (Convert-FolderSeparator "$PSScriptRoot/Parameters/build.psd1") -Verbose -OutVariable Output + | Out-Host + $Output | Should -Not -BeNullOrEmpty + $Output.Path | Convert-FolderSeparator | Should -Be (Convert-FolderSeparator "$PSScriptRoot/Result3/Parameters/3.0.0/Parameters.psd1") } - It "The Target is Build" { - "$PSScriptRoot\Result3\Parameters\ReadMe.md" | Should -Exist + It "The target is 'Build' (not CleanBuild) so pre-created extra files get left behind" { + Convert-FolderSeparator "$PSScriptRoot/Result3/Parameters/3.0.0/DeleteMe.md" | Should -Exist + Convert-FolderSeparator "$PSScriptRoot/Result3/Parameters/3.0.0/Parameters.psm1" | Should -Exist } It "The version is set" { + $Metadata = Import-Metadata "$PSScriptRoot/Result3/Parameters/3.0.0/Parameters.psd1" $Metadata.ModuleVersion | Should -Be "3.0.0" } It "The PreRelease is set" { + $Metadata = Import-Metadata "$PSScriptRoot/Result3/Parameters/3.0.0/Parameters.psd1" $Metadata.PrivateData.PSData.Prerelease | Should -Be 'alpha001' } } diff --git a/Tests/Integration/Parameters/Parameters.psd1 b/Tests/Integration/Parameters/Parameters.psd1 index fa936e4..b9f4d2b 100644 --- a/Tests/Integration/Parameters/Parameters.psd1 +++ b/Tests/Integration/Parameters/Parameters.psd1 @@ -1,6 +1,6 @@ @{ # The module version should be SemVer.org compatible - ModuleVersion = "1.0.0" + ModuleVersion = "3.0.0" # PrivateData is where all third-party metadata goes PrivateData = @{ diff --git a/Tests/Integration/Result3/Parameters/ReadMe.md b/Tests/Integration/Result3/Parameters/ReadMe.md deleted file mode 100644 index e69de29..0000000 diff --git a/Tests/Integration/Source1.Tests.ps1 b/Tests/Integration/Source1.Tests.ps1 index 63b5bb9..bcb285a 100644 --- a/Tests/Integration/Source1.Tests.ps1 +++ b/Tests/Integration/Source1.Tests.ps1 @@ -2,14 +2,16 @@ . $PSScriptRoot\..\Convert-FolderSeparator.ps1 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 + $Module = [IO.Path]::ChangeExtension($Output.Path, "psm1") + $Metadata = Import-Metadata $Output.Path + } 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,14 +31,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 + $Module = [IO.Path]::ChangeExtension($Output.Path, "psm1") + $Metadata = Import-Metadata $Output.Path + } 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 @() @@ -80,9 +84,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 +95,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 +165,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 +216,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 @@ -240,13 +251,16 @@ Describe "Supports building discovering the module without a build.psd1" -Tag In $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" { $Module = [IO.Path]::ChangeExtension($Output.Path, "psm1") @@ -256,20 +270,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 + BeforeAll { + Copy-Item $PSScriptRoot\Source1 TestDrive:\Source1 -Recurse - New-Item TestDrive:\Source1\_GlobalScope.ps1 -Value '$Global:Module = "Testing"' + New-Item TestDrive:\Source1\_GlobalScope.ps1 -Value '$Global:Module = "Testing"' - $metadata = Import-Metadata TestDrive:\Source1\build.psd1 - $metadata += @{ - Prefix = "./_GlobalScope.ps1" - Suffix = "./_GlobalScope.ps1" - } - $metadata | Export-Metadata TestDrive:\Source1\build.psd1 + $metadata = Import-Metadata TestDrive:\Source1\build.psd1 + $metadata += @{ + Prefix = "./_GlobalScope.ps1" + Suffix = "./_GlobalScope.ps1" + } + $metadata | Export-Metadata TestDrive:\Source1\build.psd1 - $Output = Build-Module TestDrive:\Source1 -Passthru + $Output = Build-Module TestDrive:\Source1 -Passthru - $Metadata = Import-Metadata $Output.Path + $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") @@ -288,10 +304,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 +319,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/Private/ConvertToAst.Tests.ps1 b/Tests/Private/ConvertToAst.Tests.ps1 index 5ccca7e..a53545e 100644 --- a/Tests/Private/ConvertToAst.Tests.ps1 +++ b/Tests/Private/ConvertToAst.Tests.ps1 @@ -2,8 +2,10 @@ Describe "ConvertToAst" { Context "It returns a ParseResult for file paths" { - $ParseResult = InModuleScope ModuleBuilder { - ConvertToAst $PSCommandPath + BeforeAll { + $ParseResult = InModuleScope ModuleBuilder { + ConvertToAst $PSCommandPath + } } It "Returns a ParseResult object" { @@ -22,8 +24,10 @@ Describe "ConvertToAst" { } 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 +37,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..a8aa80e 100644 --- a/Tests/Private/CopyReadMe.Tests.ps1 +++ b/Tests/Private/CopyReadMe.Tests.ps1 @@ -1,6 +1,8 @@ #requires -Module ModuleBuilder Describe "Copy ReadMe" { - . $PSScriptRoot\..\Convert-FolderSeparator.ps1 + BeforeAll { + . $PSScriptRoot\..\Convert-FolderSeparator.ps1 + } Context "There's no ReadMe" { # It should not even call Test-Path @@ -30,32 +32,36 @@ Describe "Copy ReadMe" { } Context "There is a ReadMe" { - # Nothing is actually created when this test runs - Mock New-Item -ModuleName ModuleBuilder - Mock Copy-Item -ModuleName ModuleBuilder + BeforeAll { + # 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 + # 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 - 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") - } + } -Scope Context } 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") - } + } -Scope Context } - Remove-Item TestDrive:\En -Recurse -Force -ErrorAction SilentlyContinue + AfterAll { + Remove-Item TestDrive:\En -Recurse -Force -ErrorAction SilentlyContinue + } } } diff --git a/Tests/Private/GetBuildInfo.Tests.ps1 b/Tests/Private/GetBuildInfo.Tests.ps1 index ab0ba09..0bb269c 100644 --- a/Tests/Private/GetBuildInfo.Tests.ps1 +++ b/Tests/Private/GetBuildInfo.Tests.ps1 @@ -1,37 +1,40 @@ #requires -Module ModuleBuilder Describe "GetBuildInfo" { - . $PSScriptRoot\..\Convert-FolderSeparator.ps1 + BeforeAll { + . $PSScriptRoot\..\Convert-FolderSeparator.ps1 - Mock Import-Metadata -ModuleName ModuleBuilder { - @{ - #Omitting path to let it resolve [Path = "MyModule.psd1"] - SourceDirectories = "Classes", "Public" + Mock Import-Metadata -ModuleName ModuleBuilder { + @{ + #Omitting path to let it resolve [Path = "MyModule.psd1"] + SourceDirectories = "Classes", "Public" + } } } Context "It 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' }" + New-ModuleManifest "TestDrive:\MyModule\Source\MyModule.psd1" -Author Tester - # 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 - - $Result = InModuleScope -ModuleName ModuleBuilder { + $Result = InModuleScope -ModuleName 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' } } } @@ -39,7 +42,7 @@ Describe "GetBuildInfo" { It "Parses the build.psd1" { Assert-MockCalled Import-Metadata -ModuleName ModuleBuilder -ParameterFilter { (Convert-FolderSeparator $Path) -eq (Convert-FolderSeparator "TestDrive:\MyModule\Source\build.psd1") - } + } -Scope Context } It "Reads bound parameters from the BuildCommandInvocation" { diff --git a/Tests/Private/GetRelativePath.Tests.ps1 b/Tests/Private/GetRelativePath.Tests.ps1 index e11aae9..086668a 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/ImportModuleManifest.Tests.ps1 b/Tests/Private/ImportModuleManifest.Tests.ps1 index 7c7dbf1..71a525a 100644 --- a/Tests/Private/ImportModuleManifest.Tests.ps1 +++ b/Tests/Private/ImportModuleManifest.Tests.ps1 @@ -2,7 +2,9 @@ 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..8731735 100644 --- a/Tests/Private/InitializeBuild.Tests.ps1 +++ b/Tests/Private/InitializeBuild.Tests.ps1 @@ -1,20 +1,23 @@ #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" + New-ModuleManifest "TestDrive:\Source\MyModule.psd1" -RootModule "MyModule.psm1" -Author "Test Manager" -ModuleVersion "1.0.0" - $Result = @{} + $Result = @{} + } It "Handles Build-Module parameters, and the build.psd1 configuration" { Push-Location TestDrive:\ @@ -43,30 +46,33 @@ Describe "InitializeBuild" { 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") + It "Returns overridden values from the build manifest" { + $Result.Result.SourceDirectories | Should -Be @("Classes", "Private", "Public") } - It "Returns overriden values from parameters" { - $Result.SourcePath | Should -Be (Convert-Path 'TestDrive:\Source\MyModule.psd1') + It "Returns overridden values from parameters" { + $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/MoveUsingStatements.Tests.ps1 b/Tests/Private/MoveUsingStatements.Tests.ps1 index e2f8f8e..2cf5d73 100644 --- a/Tests/Private/MoveUsingStatements.Tests.ps1 +++ b/Tests/Private/MoveUsingStatements.Tests.ps1 @@ -1,7 +1,10 @@ #requires -Module ModuleBuilder Describe "MoveUsingStatements" { - Context "Necessary Parameters" { + BeforeAll { $CommandInfo = InModuleScope ModuleBuilder { Get-Command MoveUsingStatements } + } + + Context "Necessary Parameters" { It 'has a mandatory AST parameter' { $AST = $CommandInfo.Parameters['AST'] @@ -19,78 +22,79 @@ Describe "MoveUsingStatements" { } Context "Moving Using Statements to the beginning of the file" { - - $MoveUsingStatementsCmd = InModuleScope ModuleBuilder { - $null = Mock Write-Warning { } - { param($RootModule) - ConvertToAst $RootModule | MoveUsingStatements + BeforeAll { + $MoveUsingStatementsCmd = InModuleScope ModuleBuilder { + $null = Mock Write-Warning { } + { param($RootModule) + ConvertToAst $RootModule | MoveUsingStatements + } } - } - $TestCases = @( - @{ - TestCaseName = 'Moves all using statements in `n terminated files to the top' - PSM1File = "function x {`n}`n" + - "using namespace System.IO`n`n" + #UsingMustBeAtStartOfScript - "function y {`n}`n" + - "using namespace System.Drawing" #UsingMustBeAtStartOfScript - ErrorBefore = 2 - ErrorAfter = 0 - }, - @{ - TestCaseName = 'Moves all using statements in`r`n terminated files to the top' - PSM1File = "function x {`r`n}`r`n" + - "USING namespace System.IO`r`n`r`n" + #UsingMustBeAtStartOfScript - "function y {`r`n}`r`n" + - "USING namespace System.Drawing" #UsingMustBeAtStartOfScript - ErrorBefore = 2 - ErrorAfter = 0 - }, - @{ - TestCaseName = 'Prevents duplicate using statements' - PSM1File = "using namespace System.IO`r`n" + #UsingMustBeAtStartOfScript - "function x {`r`n}`r`n`r`n" + - "using namespace System.IO`r`n" + #UsingMustBeAtStartOfScript - "function y {`r`n}`r`n" + - "USING namespace System.IO" #UsingMustBeAtStartOfScript - ExpectedResult = "using namespace System.IO`r`n" + - "#using namespace System.IO`r`n" + - "function x {`r`n}`r`n`r`n" + - "#using namespace System.IO`r`n" + - "function y {`r`n}`r`n" + - "#USING namespace System.IO" - ErrorBefore = 2 - ErrorAfter = 0 - }, - @{ - TestCaseName = 'Does not change the content again if there are no out-of-place using statements' - PSM1File = "using namespace System.IO`r`n`r`n" + - "using namespace System.Drawing`r`n" + - "function x {`r`n}`r`n" + - "function y {`r`n}`r`n" - ErrorBefore = 0 - ErrorAfter = 0 - }, - @{ - TestCaseName = 'Moves using statements even if types are used' - PSM1File = "function x {`r`n}`r`n" + - "using namespace System.IO`r`n`r`n" + #UsingMustBeAtStartOfScript - "function y {`r`n}`r`n" + - "using namespace System.Collections.Generic" + #UsingMustBeAtStartOfScript - "function z { [Dictionary[String,PSObject]]::new() }" #TypeNotFound - ErrorBefore = 3 - ErrorAfter = 0 - }, - @{ - TestCaseName = 'Moves using statements even when there are (other) parse errors' - PSM1File = "using namespace System.IO`r`n`r`n" + - "function x {`r`n}`r`n" + - "using namespace System.Drawing`r`n" + # UsingMustBeAtStartOfScript - "function y {`r`n}`r`n}" # Extra } at the end - ErrorBefore = 2 - ErrorAfter = 1 - } - ) + $TestCases = @( + @{ + TestCaseName = 'Moves all using statements in `n terminated files to the top' + PSM1File = "function x {`n}`n" + + "using namespace System.IO`n`n" + #UsingMustBeAtStartOfScript + "function y {`n}`n" + + "using namespace System.Drawing" #UsingMustBeAtStartOfScript + ErrorBefore = 2 + ErrorAfter = 0 + }, + @{ + TestCaseName = 'Moves all using statements in`r`n terminated files to the top' + PSM1File = "function x {`r`n}`r`n" + + "USING namespace System.IO`r`n`r`n" + #UsingMustBeAtStartOfScript + "function y {`r`n}`r`n" + + "USING namespace System.Drawing" #UsingMustBeAtStartOfScript + ErrorBefore = 2 + ErrorAfter = 0 + }, + @{ + TestCaseName = 'Prevents duplicate using statements' + PSM1File = "using namespace System.IO`r`n" + #UsingMustBeAtStartOfScript + "function x {`r`n}`r`n`r`n" + + "using namespace System.IO`r`n" + #UsingMustBeAtStartOfScript + "function y {`r`n}`r`n" + + "USING namespace System.IO" #UsingMustBeAtStartOfScript + ExpectedResult = "using namespace System.IO`r`n" + + "#using namespace System.IO`r`n" + + "function x {`r`n}`r`n`r`n" + + "#using namespace System.IO`r`n" + + "function y {`r`n}`r`n" + + "#USING namespace System.IO" + ErrorBefore = 2 + ErrorAfter = 0 + }, + @{ + TestCaseName = 'Does not change the content again if there are no out-of-place using statements' + PSM1File = "using namespace System.IO`r`n`r`n" + + "using namespace System.Drawing`r`n" + + "function x {`r`n}`r`n" + + "function y {`r`n}`r`n" + ErrorBefore = 0 + ErrorAfter = 0 + }, + @{ + TestCaseName = 'Moves using statements even if types are used' + PSM1File = "function x {`r`n}`r`n" + + "using namespace System.IO`r`n`r`n" + #UsingMustBeAtStartOfScript + "function y {`r`n}`r`n" + + "using namespace System.Collections.Generic" + #UsingMustBeAtStartOfScript + "function z { [Dictionary[String,PSObject]]::new() }" #TypeNotFound + ErrorBefore = 3 + ErrorAfter = 0 + }, + @{ + TestCaseName = 'Moves using statements even when there are (other) parse errors' + PSM1File = "using namespace System.IO`r`n`r`n" + + "function x {`r`n}`r`n" + + "using namespace System.Drawing`r`n" + # UsingMustBeAtStartOfScript + "function y {`r`n}`r`n}" # Extra } at the end + ErrorBefore = 2 + ErrorAfter = 1 + } + ) + } It '' -TestCases $TestCases { param($TestCaseName, $PSM1File, $ErrorBefore, $ErrorAfter, $ExpectedResult) @@ -123,14 +127,15 @@ Describe "MoveUsingStatements" { } Context "When MoveUsingStatements should do nothing" { - - $MoveUsingStatementsCmd = 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 + BeforeAll { + $MoveUsingStatementsCmd = 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 + } } } diff --git a/Tests/Private/ParseLineNumber.Tests.ps1 b/Tests/Private/ParseLineNumber.Tests.ps1 index 40fa5f2..e0c503c 100644 --- a/Tests/Private/ParseLineNumber.Tests.ps1 +++ b/Tests/Private/ParseLineNumber.Tests.ps1 @@ -45,7 +45,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/ResolveOutputFolder.Tests.ps1 b/Tests/Private/ResolveOutputFolder.Tests.ps1 index 40b95b0..c6f1f87 100644 --- a/Tests/Private/ResolveOutputFolder.Tests.ps1 +++ b/Tests/Private/ResolveOutputFolder.Tests.ps1 @@ -1,59 +1,61 @@ #requires -Module ModuleBuilder Describe "ResolveOutputFolder" { - . $PSScriptRoot\..\Convert-FolderSeparator.ps1 - $CommandInTest = InModuleScope ModuleBuilder { Get-Command ResolveOutputFolder } - filter ToTestDrive { "$_".Replace($TestDrive, "TestDrive:") } + BeforeAll { + . $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" - } - ) + $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 +127,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..684489f 100644 --- a/Tests/Private/SetModuleContent.Tests.ps1 +++ b/Tests/Private/SetModuleContent.Tests.ps1 @@ -1,7 +1,9 @@ Describe "SetModuleContent" { 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,37 +32,40 @@ 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 + BeforeAll { + ${global:mock get content index} = 1 - Mock Get-Content -ModuleName ModuleBuilder { - "Script Content" - "File $((${global:mock get content index}++))" - "From $Path" - } + 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 } + 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 + 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" } + Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Private\First.ps1" } -Scope Context + Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Private\Second.ps1" } -Scope Context + Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Public\Third.ps1" } -Scope Context } It "Copies all three files into the Output" { @@ -77,36 +82,36 @@ 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" - } + BeforeAll { + ${global:mock get content index} = 1 - Mock Resolve-Path -ModuleName ModuleBuilder { - if ($path -match "TestDrive:") { - $path -replace "TestDrive:\\", ".\" - } else { - write-error "$path not found" + Mock Get-Content -ModuleName ModuleBuilder { + "Script Content" + "File $((${global:mock get content index}++))" + "From $Path" } - } -ParameterFilter { $Relative } + Mock Resolve-Path -ModuleName ModuleBuilder { + if ($path -match "TestDrive:") { + $path -replace "TestDrive:\\", ".\" + } + } -ParameterFilter { $Relative } + + InModuleScope ModuleBuilder { + $MostlyFiles = "using module Configuration", + "TestDrive:\Private\First.ps1", + "TestDrive:\Private\Second.ps1", + "TestDrive:\Public\Third.ps1", + "Export-ModuleMember Stuff" - 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 + SetModuleContent -Source $MostlyFiles -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" } + Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Private\First.ps1" } -Scope Context + Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Private\Second.ps1" } -Scope Context + Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Public\Third.ps1" } -Scope Context } It "Copies all three files into the Output" { @@ -135,36 +140,36 @@ Describe "SetModuleContent" { } 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" - } + BeforeAll { + ${global:mock get content index} = 1 - Mock Resolve-Path -ModuleName ModuleBuilder { - if ($path -match "TestDrive:") { - $path -replace "TestDrive:\\", ".\" - } else { - write-error "$path not found" + Mock Get-Content -ModuleName ModuleBuilder { + "Script Content" + "File $((${global:mock get content index}++))" + "From $Path" } - } -ParameterFilter { $Relative } + Mock Resolve-Path -ModuleName ModuleBuilder { + if ($path -match "TestDrive:") { + $path -replace "TestDrive:\\", ".\" + } + } -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 + + 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" } + Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Private\First.ps1" } -Scope Context + Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Private\Second.ps1" } -Scope Context + Assert-MockCalled Get-Content -ModuleName ModuleBuilder -ParameterFilter { $Path -eq ".\Public\Third.ps1" } -Scope Context } It "Copies all three files into the Output" { diff --git a/Tests/Public/Build-Module.Tests.ps1 b/Tests/Public/Build-Module.Tests.ps1 index 268e9fe..4dacdcb 100644 --- a/Tests/Public/Build-Module.Tests.ps1 +++ b/Tests/Public/Build-Module.Tests.ps1 @@ -1,14 +1,18 @@ #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" + } } 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 @@ -16,7 +20,7 @@ Describe "Build-Module" { $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" { @@ -79,525 +83,533 @@ 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" + Context "Testing" { + BeforeAll { + InModuleScope ModuleBuilder { + Mock MoveUsingStatements + Mock SetModuleContent + } + Mock Update-Metadata + Mock Copy-Item + Mock Set-Location - Mock New-Item { [IO.DirectoryInfo]("$TestDrive/Output/MyModule") } -Parameter { - (Convert-FolderSeparator "$Path") -eq $Mock_OutputPath -and - $ItemType -eq "Directory" -and $Force - } + Mock Join-Path { + [IO.Path]::Combine($Path, $ChildPath) + } - Mock Test-Path { $True } -Parameter { - (Convert-FolderSeparator "$Path") -eq $Mock_OutputPath -and ($PathType -notin "Any", "Leaf") - } + Mock Get-Metadata { + "First Release" + } - Mock Remove-Item -Parameter { - (Convert-FolderSeparator "$Path") -eq $Mock_OutputPath - } + $global:Mock_OutputPath = Convert-FolderSeparator "TestDrive:/Output/MyModule" - Context "When run without parameters" { - Push-Location TestDrive:/ -StackName BuildModuleTest - New-Item -ItemType Directory -Path TestDrive:/Output/MyModule/1.0.0/ -Force + Mock New-Item { [IO.DirectoryInfo]("$TestDrive/Output/MyModule") } -Parameter { + (Convert-FolderSeparator "$Path") -eq $Mock_OutputPath -and + $ItemType -eq "Directory" -and $Force + } - Mock ConvertToAst { - [PSCustomObject]@{ - PSTypeName = "PoshCode.ModuleBuilder.ParseResults" - ParseErrors = $null - Tokens = $null - AST = { }.AST + Mock Test-Path { $True } -Parameter { + (Convert-FolderSeparator "$Path") -eq $Mock_OutputPath -and ($PathType -notin "Any", "Leaf") } - } - 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 Remove-Item -Parameter { + (Convert-FolderSeparator "$Path") -eq $Mock_OutputPath } } - Mock Push-Location {} - # So it doesn't have to exist - Mock Convert-Path { $Path } - Mock Get-ChildItem { - [IO.FileInfo]"$TestDrive/Output/MyModule/Public/Get-MyInfo.ps1" - } + Context "When run without parameters" { + 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 + } + } + 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" + } + } - try { - Build-Module - } finally { - Pop-Location -StackName BuildModuleTest - } + Mock Push-Location {} - # NOTE: We're not just clearing output, but the whole folder - It "Should remove the output folder if it exists" { - Assert-MockCalled Remove-Item - } + # So it doesn't have to exist + Mock Convert-Path { $Path } + Mock Get-ChildItem { + [IO.FileInfo]"$TestDrive/Output/MyModule/Public/Get-MyInfo.ps1" + } - It "Should always (re)create the OutputDirectory" { - Assert-MockCalled New-Item - } - It "Should run in the module source folder" { - Assert-MockCalled Push-Location -Parameter { - $Path -eq "TestDrive:/MyModule/" + try { + Build-Module + } finally { + Pop-Location -StackName BuildModuleTest + } } - } - It "Should call ConvertToAst to parse the module" { - Assert-MockCalled ConvertToAst - } + # NOTE: We're not just clearing output, but the whole folder + It "Should remove the output folder if it exists" { + Assert-MockCalled Remove-Item -Scope Context + } - It "Should call MoveUsingStatements to move the using statements, just in case" { - Assert-MockCalled MoveUsingStatements -Parameter { - $AST.Extent.Text -eq "{ }" + It "Should always (re)create the OutputDirectory" { + Assert-MockCalled New-Item -Scope Context } - } - It "Should call SetModuleContent to combine the source files" { - Assert-MockCalled SetModuleContent - } + It "Should run in the module source folder" { + Assert-MockCalled Push-Location -Parameter { + $Path -eq "TestDrive:/MyModule/" + } -Scope Context + } - It "Should call Update-Metadata to set the FunctionsToExport" { - Assert-MockCalled Update-Metadata -Parameter { - $PropertyName -eq "FunctionsToExport" + It "Should call ConvertToAst to parse the module" { + Assert-MockCalled ConvertToAst -Scope Context } - } - It "Should call Update-Metadata to set the AliasesToExport" { - Assert-MockCalled Update-Metadata -Parameter { - $PropertyName -eq "AliasesToExport" + It "Should call MoveUsingStatements to move the using statements, just in case" { + Assert-MockCalled MoveUsingStatements -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" + It "Should call SetModuleContent to combine the source files" { + Assert-MockCalled SetModuleContent -Scope Context } - } - Mock Convert-Path { $Path } + It "Should call Update-Metadata to set the FunctionsToExport" { + Assert-MockCalled Update-Metadata -Parameter { + $PropertyName -eq "FunctionsToExport" + } -Scope Context + } - Mock Get-ChildItem { - [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" + It "Should call Update-Metadata to set the AliasesToExport" { + Assert-MockCalled Update-Metadata -Parameter { + $PropertyName -eq "AliasesToExport" + } -Scope Context + } } - Mock Get-Item { - [PSCustomObject]@{ LastWriteTime = Get-Date } - } + Context "When run without 'Clean' in the target" { + BeforeAll { + $PSDefaultParameterValues = @{ + "Mock:ModuleName" = "ModuleBuilder" + "Assert-MockCalled:ModuleName" = "ModuleBuilder" + } - try { - Build-Module -Target Build - } finally { - Pop-Location -StackName BuildModuleTest - } + 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" + } + } - # NOTE: We're not just clearing output, but the whole folder - It "Should NOT remove the output folder" { - Assert-MockCalled Remove-Item -Times 0 - } + Mock Convert-Path { $Path } - It "Should check the dates on the output" { - Assert-MockCalled Get-Item -Times 1 - } + Mock Get-ChildItem { + [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" + } - It "Should always (re)create the OutputDirectory" { - Assert-MockCalled New-Item -Times 1 - } + Mock Get-Item { + [PSCustomObject]@{ LastWriteTime = Get-Date } + } - It "Should not rebuild the source files" { - Assert-MockCalled SetModuleContent -Times 0 - } - } + try { + Build-Module -Target Build + } finally { + Pop-Location -StackName BuildModuleTest + } + } - 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 + # NOTE: We're not just clearing output, but the whole folder + It "Should NOT remove the output folder" { + Assert-MockCalled Remove-Item -Times 0 -Scope Context } - } - Mock ImportModuleManifest { - [PSCustomObject]@{ - Name = "MyModule" - ModuleBase = "TestDrive:/MyModule/" + It "Should check the dates on the output" { + Assert-MockCalled Get-Item -Times 1 -Scope Context } - } - $global:Mock_OutputPath = Convert-FolderSeparator "TestDrive:/Output/MyModule/$ExpectedVersion" + It "Should always (re)create the OutputDirectory" { + Assert-MockCalled New-Item -Times 1 -Scope Context + } - Mock Get-ChildItem { - [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" + It "Should not rebuild the source files" { + Assert-MockCalled SetModuleContent -Times 0 -Scope Context + } } - try { - Build-Module -SemVer $SemVer - } catch { - Pop-Location -StackName BuildModuleTest - throw - } + Context "Setting the version to a SemVer string" { + 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 - It "Should build to an output folder with the simple version." { - Assert-MockCalled Remove-Item - Assert-MockCalled New-Item - } + Mock ResolveBuildManifest { "TestDrive:/MyModule/build.psd1" } - It "Should update the module version to the simple version." { - Assert-MockCalled Update-Metadata -ParameterFilter { - $PropertyName -eq "ModuleVersion" -and $Value -eq $ExpectedVersion - } - } - It "Should update the module pre-release version" { - Assert-MockCalled Update-Metadata -ParameterFilter { - $PropertyName -eq "PrivateData.PSData.Prerelease" -and $Value -eq "beta03" - } - } - 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" - } - } + Mock GetBuildInfo { + [PSCustomObject]@{ + OutputDirectory = "TestDrive:/Output" + SourcePath = "TestDrive:/MyModule/" + SemVer = "1.0.0-beta03+sha.22c35ffff166f34addc49a3b80e622b543199cc5.Date.2018-10-11" + Version = [Version]"1.0.0" + Prerelease = "beta03" + Target = "CleanBuild" + CopyPaths = @() + Encoding = "UTF8" + PublicFilter = "Public/*.ps1" + VersionedOutputDirectory = $true + } + } + + Mock ImportModuleManifest { + [PSCustomObject]@{ + Name = "MyModule" + ModuleBase = "TestDrive:/MyModule/" + } + } - 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 { "" } + $global:Mock_OutputPath = Convert-FolderSeparator "TestDrive:/Output/MyModule/$ExpectedVersion" + + Mock Get-ChildItem { + [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" + } - try { Build-Module -SemVer $SemVer - } catch { + } + AfterAll { Pop-Location -StackName BuildModuleTest - throw } - Assert-MockCalled Update-Metadata -ParameterFilter { - $PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and $Value -eq "MyModule v$SemVer" + It "Should build to an output folder with the simple version." { + 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 a prefix empty line, it should insert the module name and full semver the same way" { - # If there's no release notes, but it was left uncommented - Mock Get-Metadata { " - Multi-line Release Notes - With a prefix carriage return" } + 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 - } - 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" + Assert-MockCalled Update-Metadata -ParameterFilter { + $PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and $Value -eq "MyModule v$SemVer" + } } - } - Pop-Location -StackName BuildModuleTest - } + It "When there's a prefix empty line, it should insert the module name and full semver the same way" { + # If there's no release notes, but it was left uncommented + Mock Get-Metadata { " + Multi-line Release Notes + With a prefix carriage return" } - 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" - } - } + Build-Module -SemVer $SemVer - Mock ImportModuleManifest { - [PSCustomObject]@{ - Name = "MyModule" - ModuleBase = "TestDrive:/MyModule/" + 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" + } } } - $global:Mock_OutputPath = Convert-FolderSeparator "TestDrive:/Output/MyModule" - Mock Get-ChildItem { - [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" - } + Context "Setting the version and pre-release" { + 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 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" + } + } - try { - Build-Module @SemVer - } catch { - Pop-Location -StackName BuildModuleTest - throw - } + Mock ImportModuleManifest { + [PSCustomObject]@{ + Name = "MyModule" + ModuleBase = "TestDrive:/MyModule/" + } + } - It "Should build to an output folder with the simple version." { - Assert-MockCalled Remove-Item - Assert-MockCalled New-Item - } + $global:Mock_OutputPath = Convert-FolderSeparator "TestDrive:/Output/MyModule" + Mock Get-ChildItem { + [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" + } - It "Should update the module version to the simple version." { - Assert-MockCalled Update-Metadata -ParameterFilter { - $PropertyName -eq "ModuleVersion" -and $Value -eq $ExpectedVersion - } - } - It "Should update the module pre-release version" { - Assert-MockCalled Update-Metadata -ParameterFilter { - $PropertyName -eq "PrivateData.PSData.Prerelease" -and $Value -eq "beta03" - } - } - 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" + Build-Module @SemVer } - } - 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 { + AfterAll { Pop-Location -StackName BuildModuleTest - throw } - Assert-MockCalled Update-Metadata -ParameterFilter { - $PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and - $Value -eq "MyModule v$($SemVer.Version)-$($SemVer.Prerelease)+$($SemVer.BuildMetadata)" + It "Should build to an output folder with the simple version." { + Assert-MockCalled Remove-Item -Scope Context + Assert-MockCalled New-Item -Scope Context } - } - - It "When there's a prefix empty line, it should insert the module name and full semver the same way" { - # If there's no release notes, but it was left uncommented - Mock Get-Metadata { " - Multi-line Release Notes - With a prefix carriage return" } - try { - Build-Module @SemVer - } catch { - Pop-Location -StackName BuildModuleTest - throw + It "Should update the module version to the simple version." { + Assert-MockCalled Update-Metadata -ParameterFilter { + $PropertyName -eq "ModuleVersion" -and $Value -eq $ExpectedVersion + } -Scope Context } - - Assert-MockCalled Update-Metadata -ParameterFilter { - $PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and $Value -eq " - MyModule v$($SemVer.Version)-$($SemVer.Prerelease)+$($SemVer.BuildMetadata) - Multi-line Release Notes - With a prefix carriage return" + 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 } - } - Pop-Location -StackName BuildModuleTest - } + 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 { "" } - 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" - } - } - Mock Convert-Path { $Path } - $global:Mock_OutputPath = Convert-FolderSeparator "TestDrive:/Output/MyModule" + Build-Module @SemVer - Mock Get-ChildItem { - [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" - } + Assert-MockCalled Update-Metadata -ParameterFilter { + $PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and + $Value -eq "MyModule v$($SemVer.Version)-$($SemVer.Prerelease)+$($SemVer.BuildMetadata)" + } + } - try { - Build-Module @SemVer - } catch { - Pop-Location -StackName BuildModuleTest - throw - } + It "When there's a prefix empty line, it should insert the module name and full semver the same way" { + # If there's no release notes, but it was left uncommented + Mock Get-Metadata { " + Multi-line Release Notes + With a prefix carriage return" } - It "Should build to an output folder with the simple version." { - Assert-MockCalled Remove-Item - Assert-MockCalled New-Item - } + Build-Module @SemVer - It "Should update the module version to the simple version." { - Assert-MockCalled Update-Metadata -ParameterFilter { - $PropertyName -eq "ModuleVersion" -and $Value -eq $ExpectedVersion - } - } - It "Should not change the module pre-release value" { - Assert-MockCalled Update-Metadata -Times 0 -ParameterFilter { - $PropertyName -eq "PrivateData.PSData.Prerelease" + Assert-MockCalled Update-Metadata -ParameterFilter { + $PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and $Value -eq " + MyModule v$($SemVer.Version)-$($SemVer.Prerelease)+$($SemVer.BuildMetadata) + Multi-line Release Notes + With a prefix carriage return" + } } } - 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 - - Mock GetBuildInfo { - # These are actually all the values that we need - [PSCustomObject]@{ - OutputDirectory = "TestDrive:/$Version" - Name = "MyModule" - Version = $Version - PreRelease = $PreRelease - Target = $Target - SourcePath = "TestDrive:/MyModule/" - CopyPaths = @() - Encoding = "UTF8" - PublicFilter = "Public/*.ps1" + Context "Setting the version with no pre-release" { + 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 ImportModuleManifest { - [PSCustomObject]@{ - Name = "MyModule" - ModuleBase = "TestDrive:/MyModule/" + Mock Get-ChildItem { + [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" } + + Build-Module @SemVer + } + AfterAll { + Pop-Location -StackName BuildModuleTest } - $global:Mock_OutputPath = Convert-FolderSeparator "TestDrive:/MyModule/$ExpectedVersion" + It "Should build to an output folder with the simple version." { + Assert-MockCalled Remove-Item -Scope Context + Assert-MockCalled New-Item -Scope Context + } - Mock Get-ChildItem { - [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" + 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 } - } - AfterEach { - Pop-Location -StackName BuildModuleTest } - Context "When I Build-Module -Version 1.2.3 -Prerelease pre-release" { - It "Should set the prerelese to 'pre-release'" { - Mock Update-Metadata -ParameterFilter { - $PropertyName -eq "PrivateData.PSData.Prerelease" - } -MockWith { - $Value | Should -Be "pre-release" - } + 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 - try { - Build-Module -Version "1.2.3" -Prerelease "pre-release" - } catch { - throw + Mock ImportModuleManifest { + [PSCustomObject]@{ + Name = "MyModule" + ModuleBase = "TestDrive:/MyModule/" + } } - Assert-MockCalled Update-Metadata -ParameterFilter { - $PropertyName -eq "PrivateData.PSData.Prerelease" -and $Value -eq "pre-release" + $global:Mock_OutputPath = Convert-FolderSeparator "TestDrive:/MyModule/$ExpectedVersion" + + Mock Get-ChildItem { + [IO.FileInfo]"$TestDrive/MyModule/Public/Get-MyInfo.ps1" } } - } - - - Context "When I Build-Module -SemVer 1.2.3-pre-release" { + AfterEach { + Pop-Location -StackName BuildModuleTest + } - It "Should set the prerelese to 'pre-release'" { - Mock Update-Metadata -ParameterFilter { - $PropertyName -eq "PrivateData.PSData.Prerelease" - } -MockWith { - $Value | Should -Be "pre-release" + Context "When I Build-Module -Version 1.2.3 -Prerelease pre-release" { + It "Should set the prerelese to 'pre-release'" { + Mock Update-Metadata -ParameterFilter { + $PropertyName -eq "PrivateData.PSData.Prerelease" + } -MockWith { + $Value | Should -Be "pre-release" + } + + Mock GetBuildInfo { + # These are actually all the values that we need + [PSCustomObject]@{ + OutputDirectory = "TestDrive:/$Version" + Name = "MyModule" + Version = [Version]"1.2.3" + PreRelease = "pre-release" + Target = "CleanBuild" + SourcePath = "TestDrive:/MyModule/" + CopyPaths = @() + Encoding = "UTF8" + PublicFilter = "Public/*.ps1" + } + } + + + try { + Build-Module -Version "1.2.3" -Prerelease "pre-release" + } catch { + throw + } + + Assert-MockCalled Update-Metadata -ParameterFilter { + $PropertyName -eq "PrivateData.PSData.Prerelease" -and $Value -eq "pre-release" + } -Scope It } + } - try { - Build-Module -SemVer "1.2.3-pre-release" - } catch { - throw - } - Assert-MockCalled Update-Metadata -ParameterFilter { - $PropertyName -eq "PrivateData.PSData.Prerelease" -and $Value -eq "pre-release" + Context "When I Build-Module -SemVer 1.2.3-pre-release" { + + It "Should set the prerelese to 'pre-release'" { + Mock Update-Metadata -ParameterFilter { + $PropertyName -eq "PrivateData.PSData.Prerelease" + } -MockWith { + $Value | Should -Be "pre-release" + } + + Mock GetBuildInfo { + # These are actually all the values that we need + [PSCustomObject]@{ + OutputDirectory = "TestDrive:/$Version" + Name = "MyModule" + Version = [Version]"1.2.3" + PreRelease = "pre-release" + Target = "CleanBuild" + SourcePath = "TestDrive:/MyModule/" + CopyPaths = @() + Encoding = "UTF8" + PublicFilter = "Public/*.ps1" + } + } + + try { + Build-Module -SemVer "1.2.3-pre-release" + } catch { + throw + } + + Assert-MockCalled Update-Metadata -ParameterFilter { + $PropertyName -eq "PrivateData.PSData.Prerelease" -and $Value -eq "pre-release" + } } } } } + } diff --git a/build.requires.psd1 b/build.requires.psd1 new file mode 100644 index 0000000..a5d3175 --- /dev/null +++ b/build.requires.psd1 @@ -0,0 +1,9 @@ +# https://github.com/marketplace/actions/modulefast +@{ + Configuration = ":[1.5.0, 2.0)" + Metadata = ":[1.5.1, 2.0)" + Pester = ":[5.0, 6.0)" + ModuleBuilder = ":[3.0.0, 4.0)" + PSScriptAnalyzer = ":[1.21.0, 2.0)" + InvokeBuild = ":[5.10.4, 6.0)" +}