diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index 9ca3fda6..cc37fe00 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -11,12 +11,40 @@ jobs: strategy: fail-fast: false matrix: - shell: [pwsh] - os: [ubuntu-latest, macos-latest, windows-latest] include: - - shell: powershell - os: windows-latest - name: Action-Test - [${{ matrix.os }}@${{ matrix.shell }}] + - os: ubuntu-latest + shell: pwsh + path: tests/src + testtype: SourceCode + - os: ubuntu-latest + shell: pwsh + path: tests/outputs/modules + testtype: Module + - os: macos-latest + shell: pwsh + path: tests/src + testtype: sourcecode + - os: macos-latest + shell: pwsh + path: tests/outputs/modules + testtype: module + - os: windows-latest + shell: pwsh + path: tests/src + testtype: sourcecode + - os: windows-latest + shell: pwsh + path: tests/outputs/modules + testtype: module + - os: windows-latest + shell: powershell + path: tests/src + testtype: sourcecode + - os: windows-latest + shell: powershell + path: tests/outputs/modules + testtype: module + name: Action-Test - [${{ matrix.os }}@${{ matrix.shell }}] - [${{ matrix.path }}] runs-on: ${{ matrix.os }} steps: - name: Checkout repo @@ -33,5 +61,6 @@ jobs: GITHUB_TOKEN: ${{ github.token }} with: Name: PSModuleTest - Path: tests/outputs/modules + Path: ${{ matrix.path }} Shell: ${{ matrix.shell }} + TestType: ${{ matrix.TestType }} diff --git a/README.md b/README.md index 416a069b..1b8912e1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This GitHub Action is a part of the [PSModule framework](https://github.com/PSMo ## Specifications and practices -Test-PSModule follows: +Test-PSModule enables: - [Test-Driven Development](https://testdriven.io/test-driven-development/) using [Pester](https://pester.dev) and [PSScriptAnalyzer](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/overview?view=ps-modules) @@ -15,15 +15,53 @@ Test-PSModule follows: The action runs the following the Pester test framework: - [PSScriptAnalyzer tests](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/rules/readme?view=ps-modules) - [PSModule framework tests](#psmodule-tests) -- If `RunModuleTests` is set to `true`: - - Custom module tests from the `tests` directory in the module repository. - - Module manifest tests using [Test-ModuleManifest](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/test-modulemanifest) +- If `TestType` is set to `Module`: + - The module manifest is: + - tested using [Test-ModuleManifest](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/test-modulemanifest). + - temporarily altered to version `999.0.0` to avoid version conflicts when running for dependencies of the framework. + - The module is imported. + - Custom module tests from the `tests` directory in the module repository are run. + - CodeCoverage is calculated. + - The following reports are calculated and uploaded as artifacts: + - Test suite results. + - Code coverage results. +- If `TestType` is set to `SourceCode`: + - The source code is tested with: + - `PSScriptAnalyzer` for best practices, using custom settings. + - `PSModule.SourceCode` for other PSModule standards. The action fails if any of the tests fail or it fails to run the tests. ## How to use it To use the action, create a new file in the `.github/workflows` directory of the module repository and add the following content. +
+Workflow suggestion - before module is built + +```yaml +name: Test-PSModule + +on: [push] + +jobs: + Test-PSModule: + name: Test-PSModule + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Initialize environment + uses: PSModule/Initialize-PSModule@main + + - name: Test-PSModule + uses: PSModule/Test-PSModule@main + with: + Path: src + TestType: SourceCode + +``` +
Workflow suggestion - after module is built @@ -47,8 +85,8 @@ jobs: - name: Test-PSModule uses: PSModule/Test-PSModule@main with: - Name: PSModule Path: outputs/modules + TestType: Module ```
@@ -59,9 +97,9 @@ jobs: | Name | Description | Required | Default | | ---- | ----------- | -------- | ------- | -| `Name` | The name of the module to test. The name of the repository is used if not specified. | `false` | | | `Path` | The path to the module to test. | `true` | | -| `RunModuleTests` | Run the module tests. | `false` | `true` | +| `TestType` | The type of tests to run. Can be either `Module` or `SourceCode`. | `true` | | +| `Name` | The name of the module to test. The name of the repository is used if not specified. | `false` | | | `Shell` | The shell to use for running the tests. | `false` | `pwsh` | ## PSModule tests diff --git a/action.yml b/action.yml index ee8a6a90..fc4258a6 100644 --- a/action.yml +++ b/action.yml @@ -12,10 +12,9 @@ inputs: Path: description: The path to the module to test. required: true - RunModuleTests: - description: Run the module tests. - required: false - default: 'true' + TestType: + description: The type of tests to run. Can be either 'Module' or 'SourceCode'. + required: true Shell: description: The shell to use for running the tests. required: false @@ -29,7 +28,21 @@ runs: env: GITHUB_ACTION_INPUT_Name: ${{ inputs.Name }} GITHUB_ACTION_INPUT_Path: ${{ inputs.Path }} - GITHUB_ACTION_INPUT_RunModuleTests: ${{ inputs.RunModuleTests }} + GITHUB_ACTION_INPUT_TestType: ${{ inputs.TestType }} run: | # Test-PSModule . "$env:GITHUB_ACTION_PATH\scripts\main.ps1" -Verbose + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: ${{ inputs.TestType == 'Module' }} + with: + name: ${{ runner.os }}-${{ inputs.Shell }}-Test-Report + path: ${{ github.workspace }}/outputs/Test-Report.xml + + - name: Upload code coverage report + uses: actions/upload-artifact@v4 + if: ${{ inputs.TestType == 'Module' }} + with: + name: ${{ runner.os }}-${{ inputs.Shell }}-CodeCoverage-Report + path: ${{ github.workspace }}/outputs/CodeCoverage-Report.xml diff --git a/scripts/helpers/Test-PSModule.ps1 b/scripts/helpers/Test-PSModule.ps1 index 2ec23249..15b485d8 100644 --- a/scripts/helpers/Test-PSModule.ps1 +++ b/scripts/helpers/Test-PSModule.ps1 @@ -14,32 +14,19 @@ function Test-PSModule { # Run module tests. [Parameter()] - [switch] $RunModuleTests + [ValidateSet('SourceCode', 'Module')] + [string] $TestType = 'SourceCode' ) $moduleName = Split-Path -Path $Path -Leaf - - #region Test Module Manifest - Start-LogGroup 'Test Module Manifest' - $moduleManifestPath = Join-Path -Path $Path -ChildPath "$moduleName.psd1" - if (Test-Path -Path $moduleManifestPath) { - try { - $status = Test-ModuleManifest -Path $moduleManifestPath - } catch { - Write-Warning "⚠️ Test-ModuleManifest failed: $moduleManifestPath" - throw $_.Exception.Message - } - Write-Verbose ($status | Format-List | Out-String) -Verbose - } else { - Write-Warning "⚠️ Module manifest not found: $moduleManifestPath" - } - Stop-LogGroup - #endregion + $testSourceCode = $TestType -eq 'SourceCode' + $testModule = $TestType -eq 'Module' + $moduleTestsPath = Join-Path $env:GITHUB_WORKSPACE 'tests' #region Get test kit versions Start-LogGroup 'Get test kit versions' - $PSSAModule = Get-PSResource -Name PSScriptAnalyzer | Sort-Object Version -Descending | Select-Object -First 1 - $pesterModule = Get-PSResource -Name Pester | Sort-Object Version -Descending | Select-Object -First 1 + $PSSAModule = Get-PSResource -Name PSScriptAnalyzer -Verbose:$false | Sort-Object Version -Descending | Select-Object -First 1 + $pesterModule = Get-PSResource -Name Pester -Verbose:$false | Sort-Object Version -Descending | Select-Object -First 1 Write-Verbose 'Testing with:' Write-Verbose " PowerShell $($PSVersionTable.PSVersion.ToString())" @@ -57,6 +44,7 @@ function Test-PSModule { Data = @{ Path = $Path SettingsFilePath = Join-Path $PSSATestsPath 'PSScriptAnalyzer.Tests.psd1' + Verbose = $true } } Write-Verbose 'ContainerParams:' @@ -67,11 +55,11 @@ function Test-PSModule { #region Add test - Common - PSModule Start-LogGroup 'Add test - Common - PSModule' - $PSModuleTestsPath = Join-Path -Path $env:GITHUB_ACTION_PATH -ChildPath 'scripts\tests\PSModule' $containerParams = @{ - Path = $PSModuleTestsPath + Path = Join-Path -Path $env:GITHUB_ACTION_PATH -ChildPath 'scripts\tests\PSModule\Common.Tests.ps1' Data = @{ - Path = $Path + Path = $Path + Verbose = $true } } Write-Verbose 'ContainerParams:' @@ -80,15 +68,49 @@ function Test-PSModule { Stop-LogGroup #endregion - #region Add test - Specific - $moduleName - if ($RunModuleTests) { - $moduleTestsPath = Join-Path $env:GITHUB_WORKSPACE 'tests' + #region Add test - Module - PSModule + if ($testModule) { + Start-LogGroup 'Add test - Module - PSModule' + $containerParams = @{ + Path = Join-Path -Path $env:GITHUB_ACTION_PATH -ChildPath 'scripts\tests\PSModule\Module.Tests.ps1' + Data = @{ + Path = $Path + Verbose = $true + } + } + Write-Verbose 'ContainerParams:' + Write-Verbose "$($containerParams | ConvertTo-Json)" + $containers += New-PesterContainer @containerParams + Stop-LogGroup + } + #endregion + + #region Add test - SourceCode - PSModule + if ($testSourceCode) { + Start-LogGroup 'Add test - SourceCode - PSModule' + $containerParams = @{ + Path = Join-Path -Path $env:GITHUB_ACTION_PATH -ChildPath 'scripts\tests\PSModule\SourceCode.Tests.ps1' + Data = @{ + Path = $Path + Verbose = $true + } + } + Write-Verbose 'ContainerParams:' + Write-Verbose "$($containerParams | ConvertTo-Json)" + $containers += New-PesterContainer @containerParams + Stop-LogGroup + } + #endregion + + #region Add test - Module - $moduleName + if ($testModule) { if (Test-Path -Path $moduleTestsPath) { - Start-LogGroup "Add test - Specific - $moduleName" + Start-LogGroup "Add test - Module - $moduleName" $containerParams = @{ Path = $moduleTestsPath Data = @{ - Path = $Path + Path = $Path + Verbose = $true } } Write-Verbose 'ContainerParams:' @@ -98,17 +120,17 @@ function Test-PSModule { } else { Write-Warning "⚠️ No tests found - [$moduleTestsPath]" } - } else { - Write-Warning "⚠️ Module tests are disabled - [$moduleName]" } #endregion #region Import module - if ((Test-Path -Path $moduleTestsPath) -and $RunModuleTests) { + if ((Test-Path -Path $moduleTestsPath) -and $testModule) { Start-LogGroup "Importing module: $moduleName" + $moduleManifestPath = Join-Path -Path $Path -ChildPath "$moduleName.psd1" + Set-ModuleManifest -Path $moduleManifestPath -ModuleVersion '999.0.0' Add-PSModulePath -Path (Split-Path $Path -Parent) Get-Module -Name $moduleName -ListAvailable | Remove-Module -Force - Import-Module -Name $moduleName -Force -RequiredVersion 999.0.0 -Global + Import-Module -Name $moduleName -Force -RequiredVersion '999.0.0' -Global Stop-LogGroup } #endregion @@ -123,14 +145,14 @@ function Test-PSModule { PassThru = $true } TestResult = @{ - Enabled = $true + Enabled = $testModule OutputFormat = 'NUnitXml' - OutputPath = '.\outputs\PSModuleTest.Results.xml' - TestSuiteName = 'PSModule Tests' + OutputPath = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'outputs\Test-Report.xml' + TestSuiteName = 'Unit tests' } CodeCoverage = @{ - Enabled = $true - OutputPath = '.\outputs\CodeCoverage.xml' + Enabled = $testModule + OutputPath = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'outputs\CodeCoverage-Report.xml' OutputFormat = 'JaCoCo' OutputEncoding = 'UTF8' CoveragePercentTarget = 75 diff --git a/scripts/main.ps1 b/scripts/main.ps1 index 8a6fe8bd..efa1b0a1 100644 --- a/scripts/main.ps1 +++ b/scripts/main.ps1 @@ -17,14 +17,13 @@ Write-Verbose "Code to test: [$codeToTest]" if (-not (Test-Path -Path $codeToTest)) { throw "Path [$codeToTest] does not exist." } -$runModuleTests = $env:GITHUB_ACTION_INPUT_RunModuleTests -eq 'true' -Write-Verbose "Run module tests: [$runModuleTests]" +Write-Verbose "Test type to run: [$env:GITHUB_ACTION_INPUT_TestType]" Stop-LogGroup $params = @{ - Path = $codeToTest - RunModuleTests = $runModuleTests + Path = $codeToTest + TestType = $env:GITHUB_ACTION_INPUT_TestType } $results = Test-PSModule @params diff --git a/scripts/tests/PSModule/CodingStyle.Tests.ps1 b/scripts/tests/PSModule/CodingStyle.Tests.ps1 deleted file mode 100644 index bb935ceb..00000000 --- a/scripts/tests/PSModule/CodingStyle.Tests.ps1 +++ /dev/null @@ -1,5 +0,0 @@ -Describe 'CodingStyle' { - It 'Is following the coding style for PSModule Framework' { - $true | Should -Be $true - } -} diff --git a/scripts/tests/PSModule/PSModule.Tests.ps1 b/scripts/tests/PSModule/Common.Tests.ps1 similarity index 90% rename from scripts/tests/PSModule/PSModule.Tests.ps1 rename to scripts/tests/PSModule/Common.Tests.ps1 index 49093944..9dd283b5 100644 --- a/scripts/tests/PSModule/PSModule.Tests.ps1 +++ b/scripts/tests/PSModule/Common.Tests.ps1 @@ -9,10 +9,9 @@ Param( ) # These tests are for the whole module and its parts. The scope of these tests are on the src folder and the specific module folder within it. -Context 'Module design tests' { - Describe 'Script files' { +Describe 'Script files' { + Context 'Module design tests' { It 'Script filename and function/filter name should match' { - $scriptFiles = @() Get-ChildItem -Path $Path -Filter '*.ps1' -Recurse -File | ForEach-Object { @@ -38,9 +37,7 @@ Context 'Module design tests' { } # It 'Script file should only contain one function or filter' {} - # It 'All script files have tests' {} # Look for the folder name in tests called the same as section/folder name of functions - } Describe 'Function/filter design' { @@ -68,15 +65,3 @@ Context 'Module design tests' { # It 'parameters are separated by a blank line' {} } } - -Context 'Manifest file' { - It 'has a manifest file' {} - It 'has a valid license URL' {} - It 'has a valid project URL' {} - It 'has a valid icon URL' {} - It 'has a valid help URL' {} -} - -Context 'Root module file' { - It 'has a root module file' {} -} diff --git a/scripts/tests/PSModule/Module.Tests.ps1 b/scripts/tests/PSModule/Module.Tests.ps1 new file mode 100644 index 00000000..a30684ed --- /dev/null +++ b/scripts/tests/PSModule/Module.Tests.ps1 @@ -0,0 +1,43 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSReviewUnusedParameter', 'Path', + Justification = 'Path is used to specify the path to the module to test.' +)] +[CmdLetBinding()] +Param( + [Parameter(Mandatory)] + [string] $Path +) + +BeforeAll { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', 'moduleName', + Justification = 'moduleName is used in the test.' + )] + $moduleName = Split-Path -Path $Path -Leaf +} + +Describe 'PSModule - Module tests' { + Context "Module Manifest" { + BeforeAll { + $moduleManifestPath = Join-Path -Path $Path -ChildPath "$moduleName.psd1" + Write-Verbose "Module Manifest Path: [$moduleManifestPath]" -Verbose + } + It 'Module Manifest exists' { + $result = Test-Path -Path $moduleManifestPath + $result | Should -Be $true + Write-Verbose $result -Verbose + } + It 'Module Manifest is valid' { + $result = Test-ModuleManifest -Path $moduleManifestPath + $result | Should -Not -Be $null + Write-Verbose $result -Verbose + } + # It 'has a valid license URL' {} + # It 'has a valid project URL' {} + # It 'has a valid icon URL' {} + # It 'has a valid help URL' {} + } + # Context "Root module file" { + # It 'has a root module file' {} + # } +} diff --git a/scripts/tests/PSModule/SourceCode.Tests.ps1 b/scripts/tests/PSModule/SourceCode.Tests.ps1 new file mode 100644 index 00000000..b7ab8b20 --- /dev/null +++ b/scripts/tests/PSModule/SourceCode.Tests.ps1 @@ -0,0 +1,70 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSReviewUnusedParameter', 'Path', + Justification = 'Path is used to specify the path to the module to test.' +)] +[CmdLetBinding()] +Param( + [Parameter(Mandatory)] + [string] $Path +) + +Describe 'PSModule - SourceCode tests' { + + Context 'function/filter' { + It 'Script filename and function/filter name should match' { + + $scriptFiles = @() + + Get-ChildItem -Path $Path -Filter '*.ps1' -Recurse -File | ForEach-Object { + $fileContent = Get-Content -Path $_.FullName -Raw + if ($fileContent -match '^(?:function|filter)\s+([a-zA-Z][a-zA-Z0-9-]*)') { + $functionName = $matches[1] + $fileName = $_.BaseName + $relativePath = $_.FullName.Replace($Path, '').Trim('\').Trim('/') + $scriptFiles += @{ + fileName = $fileName + filePath = $relativePath + functionName = $functionName + } + } + } + + $issues = @('') + $issues += $scriptFiles | Where-Object { $_.filename -ne $_.functionName } | ForEach-Object { + " - $($_.filePath): Function/filter name [$($_.functionName)]. Change file name or function/filter name so they match." + } + $issues -join [Environment]::NewLine | + Should -BeNullOrEmpty -Because 'the script files should be called the same as the function they contain' + } + + # It 'Script file should only contain one function or filter' {} + + # It 'All script files have tests' {} # Look for the folder name in tests called the same as section/folder name of functions + + } + + Context 'Function/filter design' { + # It 'comment based doc block start is indented with 4 spaces' {} + # It 'comment based doc is indented with 8 spaces' {} + # It 'has synopsis for all functions' {} + # It 'has description for all functions' {} + # It 'has examples for all functions' {} + # It 'has output documentation for all functions' {} + # It 'has [CmdletBinding()] attribute' {} + # It 'boolean parameters in CmdletBinding() attribute are written without assignments' {} + # I.e. [CmdletBinding(ShouldProcess)] instead of [CmdletBinding(ShouldProcess = $true)] + # It 'has [OutputType()] attribute' {} + # It 'has verb 'New','Set','Disable','Enable' etc. and uses "ShoudProcess" in the [CmdletBinding()] attribute' {} + } + + Context 'Parameter design' { + # It 'has parameter description for all functions' {} + # It 'has parameter validation for all functions' {} + # It 'parameters have [Parameters()] attribute' {} + # It 'boolean parameters to the [Parameter()] attribute are written without assignments' {} + # I.e. [Parameter(Mandatory)] instead of [Parameter(Mandatory = $true)] + # It 'datatype for parameters are written on the same line as the parameter name' {} + # It 'datatype for parameters and parameter name are separated by a single space' {} + # It 'parameters are separated by a blank line' {} + } +} diff --git a/tests/outputs/modules/PSModuleTest/PSModuleTest.psd1 b/tests/outputs/modules/PSModuleTest/PSModuleTest.psd1 index ebe988d3..b47c2dd3 100644 --- a/tests/outputs/modules/PSModuleTest/PSModuleTest.psd1 +++ b/tests/outputs/modules/PSModuleTest/PSModuleTest.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'PSModuleTest.psm1' - ModuleVersion = '999.0.0' + ModuleVersion = '0.0.1' CompatiblePSEditions = @( 'Core' 'Desktop' diff --git a/tests/src/PSModuleTest/assemblies/LsonLib.dll b/tests/src/PSModuleTest/assemblies/LsonLib.dll new file mode 100644 index 00000000..36618070 Binary files /dev/null and b/tests/src/PSModuleTest/assemblies/LsonLib.dll differ diff --git a/tests/src/PSModuleTest/classes/Book.ps1 b/tests/src/PSModuleTest/classes/Book.ps1 new file mode 100644 index 00000000..3e22c270 --- /dev/null +++ b/tests/src/PSModuleTest/classes/Book.ps1 @@ -0,0 +1,132 @@ +class Book { + # Class properties + [string] $Title + [string] $Author + [string] $Synopsis + [string] $Publisher + [datetime] $PublishDate + [int] $PageCount + [string[]] $Tags + # Default constructor + Book() { $this.Init(@{}) } + # Convenience constructor from hashtable + Book([hashtable]$Properties) { $this.Init($Properties) } + # Common constructor for title and author + Book([string]$Title, [string]$Author) { + $this.Init(@{Title = $Title; Author = $Author }) + } + # Shared initializer method + [void] Init([hashtable]$Properties) { + foreach ($Property in $Properties.Keys) { + $this.$Property = $Properties.$Property + } + } + # Method to calculate reading time as 2 minutes per page + [timespan] GetReadingTime() { + if ($this.PageCount -le 0) { + throw 'Unable to determine reading time from page count.' + } + $Minutes = $this.PageCount * 2 + return [timespan]::new(0, $Minutes, 0) + } + # Method to calculate how long ago a book was published + [timespan] GetPublishedAge() { + if ( + $null -eq $this.PublishDate -or + $this.PublishDate -eq [datetime]::MinValue + ) { throw 'PublishDate not defined' } + + return (Get-Date) - $this.PublishDate + } + # Method to return a string representation of the book + [string] ToString() { + return "$($this.Title) by $($this.Author) ($($this.PublishDate.Year))" + } +} + +class BookList { + # Static property to hold the list of books + static [System.Collections.Generic.List[Book]] $Books + # Static method to initialize the list of books. Called in the other + # static methods to avoid needing to explicit initialize the value. + static [void] Initialize() { [BookList]::Initialize($false) } + static [bool] Initialize([bool]$force) { + if ([BookList]::Books.Count -gt 0 -and -not $force) { + return $false + } + + [BookList]::Books = [System.Collections.Generic.List[Book]]::new() + + return $true + } + # Ensure a book is valid for the list. + static [void] Validate([book]$Book) { + $Prefix = @( + 'Book validation failed: Book must be defined with the Title,' + 'Author, and PublishDate properties, but' + ) -join ' ' + if ($null -eq $Book) { throw "$Prefix was null" } + if ([string]::IsNullOrEmpty($Book.Title)) { + throw "$Prefix Title wasn't defined" + } + if ([string]::IsNullOrEmpty($Book.Author)) { + throw "$Prefix Author wasn't defined" + } + if ([datetime]::MinValue -eq $Book.PublishDate) { + throw "$Prefix PublishDate wasn't defined" + } + } + # Static methods to manage the list of books. + # Add a book if it's not already in the list. + static [void] Add([Book]$Book) { + [BookList]::Initialize() + [BookList]::Validate($Book) + if ([BookList]::Books.Contains($Book)) { + throw "Book '$Book' already in list" + } + + $FindPredicate = { + param([Book]$b) + + $b.Title -eq $Book.Title -and + $b.Author -eq $Book.Author -and + $b.PublishDate -eq $Book.PublishDate + }.GetNewClosure() + if ([BookList]::Books.Find($FindPredicate)) { + throw "Book '$Book' already in list" + } + + [BookList]::Books.Add($Book) + } + # Clear the list of books. + static [void] Clear() { + [BookList]::Initialize() + [BookList]::Books.Clear() + } + # Find a specific book using a filtering scriptblock. + static [Book] Find([scriptblock]$Predicate) { + [BookList]::Initialize() + return [BookList]::Books.Find($Predicate) + } + # Find every book matching the filtering scriptblock. + static [Book[]] FindAll([scriptblock]$Predicate) { + [BookList]::Initialize() + return [BookList]::Books.FindAll($Predicate) + } + # Remove a specific book. + static [void] Remove([Book]$Book) { + [BookList]::Initialize() + [BookList]::Books.Remove($Book) + } + # Remove a book by property value. + static [void] RemoveBy([string]$Property, [string]$Value) { + [BookList]::Initialize() + $Index = [BookList]::Books.FindIndex({ + param($b) + $b.$Property -eq $Value + }.GetNewClosure()) + if ($Index -ge 0) { + [BookList]::Books.RemoveAt($Index) + } + } +} diff --git a/tests/src/PSModuleTest/data/Config.psd1 b/tests/src/PSModuleTest/data/Config.psd1 new file mode 100644 index 00000000..fea44669 --- /dev/null +++ b/tests/src/PSModuleTest/data/Config.psd1 @@ -0,0 +1,3 @@ +@{ + RandomKey = 'RandomValue' +} diff --git a/tests/src/PSModuleTest/data/Settings.psd1 b/tests/src/PSModuleTest/data/Settings.psd1 new file mode 100644 index 00000000..bcfa7b47 --- /dev/null +++ b/tests/src/PSModuleTest/data/Settings.psd1 @@ -0,0 +1,3 @@ +@{ + RandomSetting = 'RandomSettingValue' +} diff --git a/tests/src/PSModuleTest/finally.ps1 b/tests/src/PSModuleTest/finally.ps1 new file mode 100644 index 00000000..e51c2260 --- /dev/null +++ b/tests/src/PSModuleTest/finally.ps1 @@ -0,0 +1,3 @@ +Write-Verbose '------------------------------' -Verbose +Write-Verbose '--- THIS IS A LAST LOADER ---' -Verbose +Write-Verbose '------------------------------' -Verbose diff --git a/tests/src/PSModuleTest/formats/CultureInfo.Format.ps1xml b/tests/src/PSModuleTest/formats/CultureInfo.Format.ps1xml new file mode 100644 index 00000000..a715e08a --- /dev/null +++ b/tests/src/PSModuleTest/formats/CultureInfo.Format.ps1xml @@ -0,0 +1,37 @@ + + + + + System.Globalization.CultureInfo + + System.Globalization.CultureInfo + + + + + 16 + + + 16 + + + + + + + + LCID + + + Name + + + DisplayName + + + + + + + + diff --git a/tests/src/PSModuleTest/formats/Mygciview.Format.ps1xml b/tests/src/PSModuleTest/formats/Mygciview.Format.ps1xml new file mode 100644 index 00000000..4c972c2c --- /dev/null +++ b/tests/src/PSModuleTest/formats/Mygciview.Format.ps1xml @@ -0,0 +1,65 @@ + + + + + mygciview + + System.IO.DirectoryInfo + System.IO.FileInfo + + + PSParentPath + + + + + + 7 + Left + + + + 26 + Right + + + + 26 + Right + + + + 14 + Right + + + + Left + + + + + + + + ModeWithoutHardLink + + + LastWriteTime + + + CreationTime + + + Length + + + Name + + + + + + + + diff --git a/tests/src/PSModuleTest/header.ps1 b/tests/src/PSModuleTest/header.ps1 new file mode 100644 index 00000000..cc1fde9a --- /dev/null +++ b/tests/src/PSModuleTest/header.ps1 @@ -0,0 +1,3 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Contains long links.')] +[CmdletBinding()] +param() diff --git a/tests/src/PSModuleTest/init/initializer.ps1 b/tests/src/PSModuleTest/init/initializer.ps1 new file mode 100644 index 00000000..f4121d25 --- /dev/null +++ b/tests/src/PSModuleTest/init/initializer.ps1 @@ -0,0 +1,3 @@ +Write-Verbose '-------------------------------' -Verbose +Write-Verbose '--- THIS IS AN INITIALIZER ---' -Verbose +Write-Verbose '-------------------------------' -Verbose diff --git a/tests/src/PSModuleTest/modules/OtherPSModule.psm1 b/tests/src/PSModuleTest/modules/OtherPSModule.psm1 new file mode 100644 index 00000000..9e4353ba --- /dev/null +++ b/tests/src/PSModuleTest/modules/OtherPSModule.psm1 @@ -0,0 +1,19 @@ +Function Get-OtherPSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .DESCRIPTION + A longer description of the function. + + .EXAMPLE + Get-OtherPSModule -Name 'World' + #> + [CmdletBinding()] + param( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/src/PSModuleTest/private/Get-InternalPSModule.ps1 b/tests/src/PSModuleTest/private/Get-InternalPSModule.ps1 new file mode 100644 index 00000000..3366e44b --- /dev/null +++ b/tests/src/PSModuleTest/private/Get-InternalPSModule.ps1 @@ -0,0 +1,18 @@ +Function Get-InternalPSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/src/PSModuleTest/private/Set-InternalPSModule.ps1 b/tests/src/PSModuleTest/private/Set-InternalPSModule.ps1 new file mode 100644 index 00000000..11c2fa15 --- /dev/null +++ b/tests/src/PSModuleTest/private/Set-InternalPSModule.ps1 @@ -0,0 +1,22 @@ +Function Set-InternalPSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', + Justification = 'Reason for suppressing' + )] + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/src/PSModuleTest/public/Get-PSModuleTest.ps1 b/tests/src/PSModuleTest/public/Get-PSModuleTest.ps1 new file mode 100644 index 00000000..0e9aacfe --- /dev/null +++ b/tests/src/PSModuleTest/public/Get-PSModuleTest.ps1 @@ -0,0 +1,20 @@ +#Requires -Modules Utilities + +function Get-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/src/PSModuleTest/public/New-PSModuleTest.ps1 b/tests/src/PSModuleTest/public/New-PSModuleTest.ps1 new file mode 100644 index 00000000..7f26215f --- /dev/null +++ b/tests/src/PSModuleTest/public/New-PSModuleTest.ps1 @@ -0,0 +1,24 @@ +#Requires -Modules @{ModuleName='PSSemVer'; ModuleVersion='1.0'} + +function New-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', + Justification = 'Reason for suppressing' + )] + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/src/PSModuleTest/public/Set-PSModuleTest.ps1 b/tests/src/PSModuleTest/public/Set-PSModuleTest.ps1 new file mode 100644 index 00000000..a87ac117 --- /dev/null +++ b/tests/src/PSModuleTest/public/Set-PSModuleTest.ps1 @@ -0,0 +1,22 @@ +function Set-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', + Justification = 'Reason for suppressing' + )] + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/src/PSModuleTest/public/Test-PSModuleTest.ps1 b/tests/src/PSModuleTest/public/Test-PSModuleTest.ps1 new file mode 100644 index 00000000..26be2b9b --- /dev/null +++ b/tests/src/PSModuleTest/public/Test-PSModuleTest.ps1 @@ -0,0 +1,18 @@ +function Test-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/src/PSModuleTest/scripts/loader.ps1 b/tests/src/PSModuleTest/scripts/loader.ps1 new file mode 100644 index 00000000..29ad42f6 --- /dev/null +++ b/tests/src/PSModuleTest/scripts/loader.ps1 @@ -0,0 +1,3 @@ +Write-Verbose '-------------------------' -Verbose +Write-Verbose '--- THIS IS A LOADER ---' -Verbose +Write-Verbose '-------------------------' -Verbose diff --git a/tests/src/PSModuleTest/types/DirectoryInfo.Types.ps1xml b/tests/src/PSModuleTest/types/DirectoryInfo.Types.ps1xml new file mode 100644 index 00000000..aef538b2 --- /dev/null +++ b/tests/src/PSModuleTest/types/DirectoryInfo.Types.ps1xml @@ -0,0 +1,21 @@ + + + + System.IO.FileInfo + + + Status + Success + + + + + System.IO.DirectoryInfo + + + Status + Success + + + + diff --git a/tests/src/PSModuleTest/types/FileInfo.Types.ps1xml b/tests/src/PSModuleTest/types/FileInfo.Types.ps1xml new file mode 100644 index 00000000..4cfaf6b8 --- /dev/null +++ b/tests/src/PSModuleTest/types/FileInfo.Types.ps1xml @@ -0,0 +1,14 @@ + + + + System.IO.FileInfo + + + Age + + ((Get-Date) - ($this.CreationTime)).Days + + + + +