diff --git a/.github/workflows/toolbox-alignment.yml b/.github/workflows/toolbox-alignment.yml index aa886d7..e37f7c4 100644 --- a/.github/workflows/toolbox-alignment.yml +++ b/.github/workflows/toolbox-alignment.yml @@ -3,9 +3,9 @@ name: Standalone toolbox alignment on: push: branches: [master] - paths: ['New-StringConversion.ps1', 'Test-IsEmail.ps1', 'Test-IsValidDn.ps1', 'Get-ElapsedTime.ps1', 'Private/**', 'Tests/ToolboxAlignment.Tests.ps1', '.github/workflows/toolbox-alignment.yml'] + paths: ['New-StringConversion.ps1', 'Test-IsEmail.ps1', 'Test-IsValidDn.ps1', 'Get-ElapsedTime.ps1', 'New-Timer.ps1', 'Stop-Timer.ps1', 'Get-TimerStatus.ps1', 'Private/**', 'Tests/ToolboxAlignment.Tests.ps1', 'Tests/Timer.Tests.ps1', '.github/workflows/toolbox-alignment.yml'] pull_request: - paths: ['New-StringConversion.ps1', 'Test-IsEmail.ps1', 'Test-IsValidDn.ps1', 'Get-ElapsedTime.ps1', 'Private/**', 'Tests/ToolboxAlignment.Tests.ps1', '.github/workflows/toolbox-alignment.yml'] + paths: ['New-StringConversion.ps1', 'Test-IsEmail.ps1', 'Test-IsValidDn.ps1', 'Get-ElapsedTime.ps1', 'New-Timer.ps1', 'Stop-Timer.ps1', 'Get-TimerStatus.ps1', 'Private/**', 'Tests/ToolboxAlignment.Tests.ps1', 'Tests/Timer.Tests.ps1', '.github/workflows/toolbox-alignment.yml'] workflow_dispatch: permissions: @@ -32,7 +32,7 @@ jobs: Install-Module Pester -RequiredVersion 5.7.1 -Scope CurrentUser -Force -Repository PSGallery Import-Module Pester -RequiredVersion 5.7.1 -Force $configuration = New-PesterConfiguration - $configuration.Run.Path = @('./Tests/New-ApiRequest.Tests.ps1', './Tests/ToolboxAlignment.Tests.ps1') + $configuration.Run.Path = @('./Tests/New-ApiRequest.Tests.ps1', './Tests/ToolboxAlignment.Tests.ps1', './Tests/Timer.Tests.ps1') $configuration.Run.Exit = $true $configuration.Output.Verbosity = 'Detailed' Invoke-Pester -Configuration $configuration diff --git a/Get-TimerStatus.ps1 b/Get-TimerStatus.ps1 index cf7998e..5aec3b5 100644 --- a/Get-TimerStatus.ps1 +++ b/Get-TimerStatus.ps1 @@ -17,13 +17,15 @@ function Get-TimerStatus System.Boolean #> + [CmdletBinding()] [OutputType([bool])] param ( [Parameter(Mandatory = $true)] + [ValidateNotNull()] [System.Diagnostics.Stopwatch] $Timer ) return $Timer.IsRunning -} \ No newline at end of file +} diff --git a/Stop-Timer.ps1 b/Stop-Timer.ps1 index c480b5f..2997ee9 100644 --- a/Stop-Timer.ps1 +++ b/Stop-Timer.ps1 @@ -1,73 +1,36 @@ function Stop-Timer { <# - .SYNOPSIS - Function will halt a stopwatch. - - .DESCRIPTION - Function requires a [System.Diagnostics.Stopwatch] object as input and will invoke the stop() method to hald its execution. - - If no exceptions are returned function will return $True. - - .PARAMETER Timer - A [System.Diagnostics.Stopwatch] representing the stopwatch to stop. - - .EXAMPLE - PS C:\> Stop-Timer -Timer $Timer - - .OUTPUTS - System.Boolean + .SYNOPSIS + Stops a stopwatch without resetting its elapsed time. + .DESCRIPTION + Returns $true after stopping the supplied stopwatch. Stopping an already + stopped stopwatch also succeeds. Invalid input is rejected during binding. + If Stop() throws, reports a warning and returns $false. + .PARAMETER Timer + The stopwatch instance to stop. + .EXAMPLE + Stop-Timer -Timer $Timer + .OUTPUTS + System.Boolean #> - + [CmdletBinding()] [OutputType([bool])] - param - ( + param ( [Parameter(Mandatory = $true)] + [ValidateNotNull()] [System.Diagnostics.Stopwatch]$Timer ) - Begin + try { - # Save current configuration - [string]$currentConfig = $ErrorActionPreference - - # Update configuration - $ErrorActionPreference = 'Stop' + $Timer.Stop() + return $true } - - Process - { - try - { - # Stop timer - $Timer.Stop() - - return $true - } - catch - { - # Save exception - [string]$reportedException = $Error[0].Exception.Message - - Write-Warning -Message 'Exception reported while halting stopwatch - Use the -Verbose parameter for more details' - - # Check we have an exception message - if ([string]::IsNullOrEmpty($reportedException) -eq $false) - { - Write-Verbose -Message $reportedException - } - else - { - Write-Verbose -Message 'No inner exception reported by Disconnect-AzureAD cmdlet' - } - - return $false - } - } - - End + catch { - # Revert back configuration - $ErrorActionPreference = $currentConfig + Write-Warning -Message 'Exception reported while stopping stopwatch; use -Verbose for details.' + Write-Verbose -Message $_.Exception.Message + return $false } -} \ No newline at end of file +} diff --git a/Tests/Timer.Tests.ps1 b/Tests/Timer.Tests.ps1 new file mode 100644 index 0000000..6051a05 --- /dev/null +++ b/Tests/Timer.Tests.ps1 @@ -0,0 +1,46 @@ +BeforeAll { + foreach ($name in 'New-Timer','Stop-Timer','Get-TimerStatus','Get-ElapsedTime') { + . (Join-Path $PSScriptRoot "../$name.ps1") + } +} +Describe 'Stopwatch lifecycle' { + It 'starts, reports status, stops and preserves elapsed time' { + $timer=New-Timer + $timer | Should -BeOfType ([System.Diagnostics.Stopwatch]) + Get-TimerStatus $timer | Should -BeTrue + [System.Threading.SpinWait]::SpinUntil([Func[bool]]{$timer.ElapsedTicks -gt 0},1000) | Should -BeTrue + Stop-Timer $timer | Should -BeTrue + Get-TimerStatus $timer | Should -BeFalse + $ticks=$timer.ElapsedTicks + $ticks | Should -BeGreaterThan 0 + Stop-Timer $timer | Should -BeTrue + $timer.ElapsedTicks | Should -Be $ticks + (Get-ElapsedTime -ElapsedTime $timer).Ticks | Should -Be $timer.Elapsed.Ticks + } + It 'does not start a stopwatch that was never started' { + $timer=[System.Diagnostics.Stopwatch]::new() + Stop-Timer $timer | Should -BeTrue + Get-TimerStatus $timer | Should -BeFalse + $timer.ElapsedTicks | Should -Be 0 + } + It 'rejects null and incompatible inputs' { + {Stop-Timer -Timer $null} | Should -Throw + {Get-TimerStatus -Timer $null} | Should -Throw + {Stop-Timer -Timer 'invalid'} | Should -Throw + {Get-TimerStatus -Timer 'invalid'} | Should -Throw + } + It 'preserves caller error preferences' { + $ErrorActionPreference='Continue' + $timer=New-Timer + Stop-Timer $timer | Should -BeTrue + $ErrorActionPreference | Should -Be 'Continue' + {Stop-Timer -Timer $null} | Should -Throw + $ErrorActionPreference | Should -Be 'Continue' + } + It 'accepts verbose diagnostics and returns a boolean' { + $timer=New-Timer + $result=Stop-Timer $timer -Verbose + $result | Should -BeOfType ([bool]) + Get-TimerStatus $timer -Verbose | Should -BeFalse + } +}