Notices: This section not yet converted to new layout. Download stats are rolling back out.

141,081

Downloads

201

Downloads of v 4.0.1-rc

1/18/2017

Last update

This is a prerelease version of Pester.

Pester

4.0.1-rc

Package test results are passing.

This package was approved as a trusted package on 1/18/2017.

Pester provides a framework for running BDD style Tests to execute and validate PowerShell commands inside of PowerShell and offers a powerful set of Mocking Functions that allow tests to mimic and mock the functionality of any command inside of a piece of PowerShell code being tested. Pester tests can execute any command or script that is accessible to a pester test file. This can include functions, Cmdlets, Modules and scripts. Pester can be run in ad hoc style in a console or it can be integrated into the Build scripts of a Continuous Integration system.

To install Pester, run the following command from the command line or from PowerShell:

C:\> choco install pester --version 4.0.1-rc --pre

To upgrade Pester, run the following command from the command line or from PowerShell:

C:\> choco upgrade pester --version 4.0.1-rc --pre

Files

Hide
  • chocolateyInstall.ps1 Show
    [CmdletBinding()]
    param ( )
    
    end
    {
        $modulePath      = Join-Path -Path $env:ProgramFiles -ChildPath WindowsPowerShell\Modules
        $targetDirectory = Join-Path -Path $modulePath -ChildPath Pester
        $scriptRoot      = Split-Path -Path $MyInvocation.MyCommand.Path -Parent
        $sourceDirectory = Join-Path -Path $scriptRoot -ChildPath Tools
    
        if ($PSVersionTable.PSVersion.Major -ge 5)
        {
            $manifestFile    = Join-Path -Path $sourceDirectory -ChildPath Pester.psd1
            $manifest        = Test-ModuleManifest -Path $manifestFile -WarningAction Ignore -ErrorAction Stop
            $targetDirectory = Join-Path -Path $targetDirectory -ChildPath $manifest.Version.ToString()
        }
    
        Update-Directory -Source $sourceDirectory -Destination $targetDirectory
        
        $binPath = Join-Path -Path $targetDirectory -ChildPath bin
        Install-ChocolateyPath $binPath
    
        if ($PSVersionTable.PSVersion.Major -lt 4)
        {
            $modulePaths = [Environment]::GetEnvironmentVariable('PSModulePath', 'Machine') -split ';'
            if ($modulePaths -notcontains $modulePath)
            {
                Write-Verbose -Message "Adding '$modulePath' to PSModulePath."
    
                $modulePaths = @(
                    $modulePath
                    $modulePaths
                )
    
                $newModulePath = $modulePaths -join ';'
    
                [Environment]::SetEnvironmentVariable('PSModulePath', $newModulePath, 'Machine')
                $env:PSModulePath += ";$modulePath"
            }
        }
    }
    
    begin
    {
        function Update-Directory
        {
            [CmdletBinding()]
            param (
                [Parameter(Mandatory = $true)]
                [string] $Source,
    
                [Parameter(Mandatory = $true)]
                [string] $Destination
            )
    
            $Source = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Source)
            $Destination = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Destination)
    
            if (-not (Test-Path -LiteralPath $Destination))
            {
                $null = New-Item -Path $Destination -ItemType Directory -ErrorAction Stop
            }
    
            try
            {
                $sourceItem = Get-Item -LiteralPath $Source -ErrorAction Stop
                $destItem = Get-Item -LiteralPath $Destination -ErrorAction Stop
    
                if ($sourceItem -isnot [System.IO.DirectoryInfo] -or $destItem -isnot [System.IO.DirectoryInfo])
                {
                    throw 'Not Directory Info'
                }
            }
            catch
            {
                throw 'Both Source and Destination must be directory paths.'
            }
    
            $sourceFiles = Get-ChildItem -Path $Source -Recurse |
                           Where-Object -FilterScript { -not $_.PSIsContainer }
    
            foreach ($sourceFile in $sourceFiles)
            {
                $relativePath = Get-RelativePath $sourceFile.FullName -RelativeTo $Source
                $targetPath = Join-Path -Path $Destination -ChildPath $relativePath
    
                $sourceHash = Get-FileHash -Path $sourceFile.FullName
                $destHash = Get-FileHash -Path $targetPath
    
                if ($sourceHash -ne $destHash)
                {
                    $targetParent = Split-Path -Path $targetPath -Parent
    
                    if (-not (Test-Path -Path $targetParent -PathType Container))
                    {
                        $null = New-Item -Path $targetParent -ItemType Directory -ErrorAction Stop
                    }
    
                    Write-Verbose -Message "Updating file $relativePath to new version."
                    Copy-Item -Path $sourceFile.FullName -Destination $targetPath -Force -ErrorAction Stop
                }
            }
    
            $targetFiles = Get-ChildItem -Path $Destination -Recurse |
                           Where-Object -FilterScript { -not $_.PSIsContainer }
    
            foreach ($targetFile in $targetFiles)
            {
                $relativePath = Get-RelativePath $targetFile.FullName -RelativeTo $Destination
                $sourcePath = Join-Path -Path $Source -ChildPath $relativePath
    
                if (-not (Test-Path $sourcePath -PathType Leaf))
                {
                    Write-Verbose -Message "Removing unknown file $relativePath from module folder."
                    Remove-Item -LiteralPath $targetFile.FullName -Force -ErrorAction Stop
                }
            }
    
        }
    
        function Get-RelativePath
        {
            param ( [string] $Path, [string] $RelativeTo )
            return $Path -replace "^$([regex]::Escape($RelativeTo))\\?"
        }
    
        function Get-FileHash
        {
            param ([string] $Path)
    
            if (-not (Test-Path -LiteralPath $Path -PathType Leaf))
            {
                return $null
            }
    
            $item = Get-Item -LiteralPath $Path
            if ($item -isnot [System.IO.FileSystemInfo])
            {
                return $null
            }
    
            $stream = $null
    
            try
            {
                $sha = New-Object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider
                $stream = $item.OpenRead()
                $bytes = $sha.ComputeHash($stream)
                return [convert]::ToBase64String($bytes)
            }
            finally
            {
                if ($null -ne $stream) { $stream.Close() }
                if ($null -ne $sha)    { $sha.Clear() }
            }
        }
    }
    
  • tools\bin\pester.bat Show
    @echo off
    SET DIR=%~dp0%
    SET ARGS=%*
    if NOT '%1'=='' SET ARGS=%ARGS:"=\"%
    if '%1'=='/?' goto usage
    if '%1'=='-?' goto usage
    if '%1'=='?' goto usage
    if '%1'=='/help' goto usage
    if '%1'=='help' goto usage
    
    @PowerShell -NonInteractive -NoProfile -ExecutionPolicy Bypass -Command ^
     "& Import-Module '%DIR%..\Pester.psm1';  & { Invoke-Pester -EnableExit %ARGS%}"
    
    goto finish
    :usage
    if NOT '%2'=='' goto help
    
    echo To run pester for tests, just call pester or runtests with no arguments
    echo.
    echo Example: pester
    echo.
    echo For Detailed help information, call pester help with a help topic. See
    echo help topic about_Pester for a list of all topics at the end
    echo.
    echo Example: pester help about_Pester
    echo.
    goto finish
    
    :help
    @PowerShell -NonInteractive -NoProfile -ExecutionPolicy Bypass -Command ^
      "& Import-Module '%DIR%..\Pester.psm1'; & { Get-Help %2}"
    
    :finish
    exit /B %errorlevel%
    
  • tools\en-US\about_BeforeEach_AfterEach.help.txt Show
    TOPIC
        about_BeforeEach_AfterEach
    
    SHORT DESCRIPTION
        Describes the BeforeEach and AfterEach commands, which run a set of commands that you specify
        before or after every It block.
    
    LONG DESCRIPTION
        The the BeforeEach and AfterEach commands in the Pester module let you define setup and teardown
        tasks that are performed at the beginning and end of every It block. This can eliminate duplication of code
        in test scripts, ensure that each test is performed on a pristine state regardless of their
        order, and perform any necessary clean-up tasks after each test.
    
        BeforeEach and AfterEach blocks may be defined inside of any Describe or Context. If they
        are present in both a Context and its parent Describe, BeforeEach blocks in the Describe scope
        are executed first, followed by BeforeEach blocks in the Context scope. AfterEach blocks are
        the reverse of this, with the Context  AfterEach blocks executing before Describe.
    
        The script blocks assigned to BeforeEach and AfterEach are dot-sourced in the Context or Describe
        which contains the current It statement, so you don't have to worry about the scope of variable
        assignments. Any variables that are assigned values within a BeforeEach block can be used inside
        the body of the It block.
    
        BeforeAll and AfterAll are used the same way as BeforeEach and AfterEach, except that they are
        executed at the beginning and end of their containing Describe or Context block.  This is
        essentially syntactic sugar for the following arrangement of code:
    
          Describe 'Something' {
            try
            {
                <BeforeAll Code Here>
    
                <Describe Body>
            }
            finally
            {
                <AfterAll Code Here>
            }
          }
    
        
      SYNTAX AND PLACEMENT
        Unlike most of the commands in a Pester script, BeforeEach, AfterEach, BeforeAll and AfterAll blocks
        apply to the entire Describe or Context scope in which they are defined, regardless of the order of
        commands inside the Describe or Context. In other words, even if an It block appears before BeforeEach
        or AfterEach in the tests file, the BeforeEach and AfterEach will still be executed.  Likewise, BeforeAll
        code will be executed at the beginning of a Context or Describe block regardless of where it is found,
        and AfterAll code will execute at the end of the Context or Describe.
    
    
      EXAMPLES
        Describe 'Testing BeforeEach and AfterEach' {
            $afterEachVariable = 'AfterEach has not been executed yet'
    
            It 'Demonstrates that BeforeEach may be defined after the It command' {
                $beforeEachVariable | Should Be 'Set in a describe-scoped BeforeEach'
                $afterEachVariable  | Should Be 'AfterEach has not been executed yet'
                $beforeAllVariable  | Should Be 'BeforeAll has been executed'
            }
    
            It 'Demonstrates that AfterEach has executed after the end of the first test' {
                $afterEachVariable | Should Be 'AfterEach has been executed'
            }
    
            BeforeEach {
                $beforeEachVariable = 'Set in a describe-scoped BeforeEach'
            }
    
            AfterEach {
                $afterEachVariable = 'AfterEach has been executed'
            }
    
            BeforeAll {
                $beforeAllVariable = 'BeforeAll has been executed'
            }
          }
    
    SEE ALSO
        about_Pester
        about_Should
        about_Mocking
        about_TestDrive
        about_about_Try_Catch_Finally
        Describe
        Context
        Should
        It
        Invoke-Pester
  • tools\en-US\about_Mocking.help.txt Show
    TOPIC
        about_Mocking
    
    SHORT DESCRIPTION
        Pester provides a set of Mocking functions making it easy to fake dependencies 
        and also to verify behavior. Using these mocking functions can allow you to 
        "shim" a data layer or mock other complex functions that already have their 
        own tests.
    
    LONG DESCRIPTION
        With the set of Mocking functions that Pester exposes, one can:
    
            - Mock the behavior of ANY PowerShell command.
            - Verify that specific commands were (or were not) called.
            - Verify the number of times a command was called with a set of specified 
    	  parameters.
    
      MOCKING FUNCTIONS
      For detailed information about the functions in the Pester module, use Get-Help.
    
    	Mock
    		Mocks the behavior of an existing command with an alternate 
    		implementation.
    
    	Assert-VerifiableMocks
    		Checks if any Verifiable Mock has not been invoked. If so, this will 
    		throw an exception.
    
    	Assert-MockCalled
    		Checks if a Mocked command has been called a certain number of times 
    		and throws an exception if it has not.
    
      EXAMPLE
        function Build ($version) {
            Write-Host "a build was run for version: $version"
        }
    
        function BuildIfChanged {
            $thisVersion = Get-Version
            $nextVersion = Get-NextVersion
            if ($thisVersion -ne $nextVersion) { Build $nextVersion }
            return $nextVersion
        }
    
        $here = Split-Path -Parent $MyInvocation.MyCommand.Path
        $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".")
        . "$here\$sut"
    
        Describe "BuildIfChanged" {
          Context "When there are Changes" {
            Mock Get-Version {return 1.1}
            Mock Get-NextVersion {return 1.2}
            Mock Build {} -Verifiable -ParameterFilter {$version -eq 1.2}
    
            $result = BuildIfChanged
    
            It "Builds the next version" {
              Assert-VerifiableMocks
            }
            It "returns the next version number" {
              $result | Should Be 1.2
            }
          }
          Context "When there are no Changes" {
            Mock Get-Version { return 1.1 }
            Mock Get-NextVersion { return 1.1 }
            Mock Build {}
    
            $result = BuildIfChanged
    
            It "Should not build the next version" {
              Assert-MockCalled Build -Times 0 -ParameterFilter {$version -eq 1.1}
            }
          }
        }
    
    
      MOCKING CALLS TO COMMANDS MADE FROM INSIDE SCRIPT MODULES
    
      Let's say you have code like this inside a script module (.psm1 file):
    
        function BuildIfChanged {
          $thisVersion = Get-Version
          $nextVersion = Get-NextVersion
          if ($thisVersion -ne $nextVersion) { Build $nextVersion }
          return $nextVersion
        }
    
        function Build ($version) {
          Write-Host "a build was run for version: $version"
        }
    
        # Actual definitions of Get-Version and Get-NextVersion are not shown here,
        # since we'll just be mocking them anyway. However, the commands do need to
        # exist in order to be mocked, so we'll stick dummy functions here
    
        function Get-Version { return 0 }
        function Get-NextVersion { return 0 }
    
        Export-ModuleMember -Function BuildIfChanged
    
      Beginning in Pester 3.0, there are two ways to write a unit test for a module that 
      mocks the calls to Get-Version and Get-NextVersion from the module's BuildIfChanged 
      command. The first is to inject mocks into a module:
    
      In these examples, the PSM1 file, MyModule.psm1 is installed in $env:PSModulePath on
      the local computer.
    
        Import-Module MyModule
    
        Describe "BuildIfChanged" {
          Context "When there are Changes" {
            Mock -ModuleName MyModule Get-Version { return 1.1 }
            Mock -ModuleName MyModule Get-NextVersion { return 1.2 }
    
            # To demonstrate that you can mock calls to commands other than functions 
            # defined in the same module, we'll mock a call to Write-Host.
          
            Mock -ModuleName MyModule Write-Host {} -Verifiable -ParameterFilter {
                $Object -eq 'a build was run for version: 1.2'
            }
    
            $result = BuildIfChanged
    
            It "Builds the next version and calls Write-Host" {
              Assert-VerifiableMocks
            }
    
            It "returns the next version number" {
              $result | Should Be 1.2
            }
          }
    
          Context "When there are no Changes" {
            Mock -ModuleName MyModule Get-Version { return 1.1 }
            Mock -ModuleName MyModule Get-NextVersion { return 1.1 }
            Mock -ModuleName MyModule Build { }
    
            $result = BuildIfChanged
    
            It "Should not build the next version" {
              Assert-MockCalled Build -ModuleName MyModule -Times 0 -ParameterFilter {
                $version -eq 1.1
              }
            }
          }
        }
    
    
      In this sample test script, all calls to Mock and Assert-MockCalled have the
      -ModuleName MyModule parameter added. This tells Pester to inject the mock into the module scope,
      which causes any calls to those commands from inside the module to execute the mock instead.
    
      When you write your test script this way, you can mock commands that are called by the module's
      internal functions. However, your test script is still limited to accessing the public, exported
      members of the module. For example, you could not call the Build function directly.
    
      The InModuleScope command causes entire sections of your test script to execute inside the targeted
      script module. This gives you access to unexported members of the module. For example:
    
        Import-Module MyModule
     
        Describe "Unit testing the module's internal Build function:" {
          InModuleScope MyModule {
            $testVersion = 5.0
            Mock Write-Host { }
    
            Build $testVersion
    
            It 'Outputs the correct message' {
              Assert-MockCalled Write-Host -ParameterFilter {
                $Object -eq "a build was run for version: $testVersion"
              }
            }
          }
        }
    
      When using InModuleScope, you no longer need to specify a ModuleName parameter when calling
      Mock or Assert-MockCalled for commands in the module. You can also directly call the Build
      function that the module does not export.
    
    SEE ALSO
      Mock
      Assert-VerifiableMocks
      Assert-MockCalled
      InModuleScope
      Describe
      Context
      It
      
      The following articles are useful for further understanding of Pester Mocks.
        Pester Mock and Test Drive, by Jakub Jareš:
          http://www.powershellmagazine.com/2014/09/30/pester-mock-and-testdrive/
        Pester and Mocking, by Mickey Gousset:
          http://www.systemcentercentral.com/day-53-pester-mocking/
        Mocking Missing Cmdlets with Pester, by Iain Brighton:
          http://virtualengine.co.uk/2015/mocking-missing-cmdlets-with-pester/
        Testing Mocked Output with Pester, by Steven Murawski:
          http://stevenmurawski.com/powershell/2014/02/testing-returned-objects-with-pester/
          
      The following articles are useful for deeper understanding of Mocking in general.
        Answer to the Question "What is the Purpose of Mock Objects" by Bert F:
          http://stackoverflow.com/a/3623574/5514075
        Mocks Aren't Stubs, by Martin Fowler:
          http://martinfowler.com/articles/mocksArentStubs.html
        The Art of Mocking, by Gil Zilberfeld:
          http://www.methodsandtools.com/archive/archive.php?id=122
    
  • tools\en-US\about_Pester.help.txt Show
    TOPIC
        about_Pester
    
    SHORT DESCRIPTION
        Pester is a test framework for Windows PowerShell. Use the Pester language 
        and its commands to write and run tests that verify that your scripts and 
        modules work as designed.
    
        Pester 3.4.0 supports Windows PowerShell 2.0 and greater.
    
    LONG DESCRIPTION
        Pester introduces a professional test framework for Windows PowerShell 
        commands. You can use Pester to test commands of any supported type, 
        including scripts, cmdlets, functions, CIM commands, workflows, and DSC 
        resources, and test these commands in modules of all types.
    
        Each Pester test compares actual to expected output using a collection of 
        comparison operators that mirror the familiar operators in Windows 
        PowerShell. In this way, Pester supports "dynamic testing", that is, it 
        tests the code while it's running, instead of just evaluating code syntax 
        ("static testing"). 
    
        Once your Pester tests are written are verified to work correctly, you can 
        run them automatically or on demand to verify that the output didn't change 
        and that any code changes did not introduce errors. You can also add your 
        tests to the build scripts of a continuous integration system, and add new 
        tests at any time.
    
    
     WHAT CAN PESTER TEST?
        Pester is designed to support "test-driven development" (TDD), in which you 
        write and run tests before writing your code, thereby using the test as a 
        code specification. 
    
        It also supports "behavior-driven development" (BDD), in which the tests 
        verify the behavior and output of the code, and the user experience, 
        independent of its implementation. This lets you change the implementation 
        and use the test to verify that the behavior is unchanged.
    
        You can use Pester to write "unit tests" that test individual functions in 
        isolation and "integration tests" that verify that functions can be used 
        together to generate expected results. 
    
        Pester creates and manages a temporary drive (PSDrive named TestDrive:) that
        you can use to simulate a file system. For more information, see 
        about_TestDrive.
    
        Pester also has "mocking" commands that replace the actual output of 
        commands with output that you specify. Mocking lets you test your commands 
        with varied input without creating and maintaining fake entries in a file 
        or database, or commenting-out and inserting code just for testing. For more
        information, see about_Mocking.
    
    
     THE PESTER LANGUAGE
        To make it easier to write tests, Pester uses a language especially designed 
        for testing. This "domain-specific language" (DSL) hides the standard 
        verb-noun syntax of PowerShell commands. 
    
        To make the language more fluent, the command parameters are positional, so 
        you don't typically use parameter names.	
    
        For example, this "gets all widgets" test uses the Pester language, 
        including its "It", "Should", and "Be" commands. The test verifies that the 
        actual output of the Get-Widget cmdlet is the same as the expected value in 
        the $allWidgets variables.
        
            It "gets all widgets" {
    	    Get-Widget | Should Be $allWidgets
            }
    
    
        To learn the Pester language, start by reading the following About and 
        cmdlet help topics:
    
        -- Describe:     Creates a required test container.
        -- Context:      Creates an optional scoped test sub-container.
        -- It:           Creates a test.
        -- about_Should  Compares actual to expected values. This topic also
                         lists all valid values of Be, which specify the
                         comparison operator used in the test.
    
    
    
     HOW TO CREATE TEST FILES
        To start using Pester, create a script and a test file that tests the 
        script. If you already have a script, you can create a test file for it.
    
        Pester test files are Windows PowerShell scripts with a .Tests.ps1 file name
        extension. The distinctive file name extension enables Pester to identify 
        tests and distinguish them from other scripts. 
    
        Typically, the test file and file it tests have the same base file name, 
        such as:
    
            New-Log.ps1
            New-Log.Tests.ps1
      
        For a quick start, use the New-Fixture cmdlet in the Pester module. It 
        creates a script with an empty function and a matching test file with a 
        valid test. 
    
        For example, this command creates a New-Log.ps1 script and a 
        New-Log.Tests.ps1 test script in the C:\Scripts\LogScripts directory.
            
    	New-Fixture -Path C:\Scripts\LogScripts -Name New-Log
    
    	    Directory: C:\Scripts\LogScripts
    
    
            Mode                LastWriteTime     Length Name
            ----                -------------     ------ ----
            -a----        4/18/2016   9:51 AM         30 New-Log.ps1
            -a----        4/18/2016   9:51 AM        262 New-Log.Tests.ps1
    
    
        The similar names do not automatically associate the test file and script 
        file. The test file must include code to import ("dot-source") the 
        functions, aliases, and variables in the script being tested into the scope 
        of the test script.
    
        For example:
           . .\New-Log.ps1
        -or-
           . C:\Scripts\LogScripts\New-Log.ps1
     
        
        Many Pester test files, including the files that New-Fixture creates, begin with these
        statements.
        
            $here = Split-Path -Parent $MyInvocation.MyCommand.Path
            $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
            . "$here\$sut"
    
        This code finds the current path of the test file at run time and saves it 
        in the $here variable. Then, it finds the script based on the path in $here. 
        This code assumes that the script has the same base name and is located in 
        the same directory as the test file.
    
        You can use any code in the test file that finds the script, but be sure 
        that the test file has the required *.Tests.ps1 file name extension.
    
    
    
     HOW TO RUN PESTER TESTS
        Pester tests are Windows PowerShell scripts (.ps1 files), so you can run 
        them at the command line, or in any editor.
    
        Pester also has an Invoke-Pester cmdlet with useful parameters. By default,
        Invoke-Pester runs all the tests in a directory and all of its subdirectories
        recursively, but you can run selected tests by specifying a script name or 
        name pattern, a test name, or a test tag. 
    
        Invoke-Pester parameters also let you save the test output in NUnitXml or 
        LegacyNUnitXml formats that are commonly used by reporting tools.
    
        For example, the following command runs all tests in the current directory 
        and all subdirectories recursively. It writes output to the host, but does 
        not generate any objects. 
    
    	Invoke-Pester 
    
        In contrast, this command runs only the tests in the New-Log.Tests.ps1 file 
        that have the 'EventVwr' tag. It writes the test results as custom objects 
        and saves them in NUnitXml format in the NewLogTests.xml file. It also runs
        an optional code coverage test to verify that all lines in the script ran
        at least once during the tests.
    
    	Invoke-Pester -Script C:\Tests\New-Log.Tests.ps1 ` 
              -Tag EventVwr -OutputFile .\NewLogTests.xml -OutputFormat NUnitXml `
              -CodeCoverage
    
    
        To run the New-Log.Tests.ps1 file that New-Fixture created, change to its 
        local directory or a parent directory, and run Invoke-Pester. You can also 
        use the Script parameter of Invoke-Pester to run only the New-Log.Tests.ps1 
        test.           
    
            PS C:\Scripts> Invoke-Pester -Script .\New-Log.Tests.ps1
     
        For more information about Invoke-Pester, type: Get-Help Invoke-Pester
    
    
     EXAMPLE
        For your first Pester test, use the New-Fixture cmdlet to create a script 
        file and matching test file.    
    
        For example:
    
            New-Fixture -Path C:\TestPester -Name Get-Hello
    
    	    Directory: C:\TestPester
    
    
            Mode                LastWriteTime         Length Name
            ----                -------------         ------ ----
            -a----        4/18/2016   9:51 AM             30 Get-Hello.ps1
            -a----        4/18/2016   9:51 AM            262 Get-Hello.Tests.ps1
    
    
        The Get-Hello.ps1 script contains an empty Get-Hello.ps1 function.
    
            function Get-Hello {}
    
        The Get-Hello.Tests.ps1 file contains an empty Pester test that is named
        for the Get-Hello function.
    
            Describe "Get-Hello" {
                It "does something useful" {
                    $true | Should Be $false
                }
            }
    
        To run the test, use Invoke-Pester. For example,
    
           Invoke-Pester C:\TestPester
    
        When you run the test, it fails by design, because Should compares $True to 
        $False using the equal operator ("Be") and $True doesn't equal $False.  
    
     
        To start testing the Get-Hello function, change $True to Get-Hello and 
        $False to "Hello". Now, the test compares the output of Get-Hello output to 
        'hello'.
    
        It should still fail, because Get-Hello doesn't return anything.
    
            Describe "New-Log" {
                It "does something useful" {
                    Get-Hello | Should Be 'Hello'
                }
            }
    
    
        To make the test pass, change the Get-Hello function so it returns 'hello'. 
        Then, in steps, change $False to more interesting values, then change the 
        Get-Hello function output to make the test pass.
    
        You can also experiment with other comparison operators, such as the BeLike 
        (supports wildcards) and BeExactly (case sensitive), and BeLikeExactly 
        operators. For more, information about comparison operators in Pester, see 
        about_Should.
    
    
     PESTER TEST OUTPUT
        When you run a test, Pester use a variation of Write-Host to write 
        color-coded text to the console. You'll quickly learn to recognize the 
        purple test names and green (passing) and red (failing) test results with 
        the elapsed time of the test.
        
             Describing Get-Profile
              [+] Gets all profiles 156ms
              [+] Gets only profiles 24ms
    
        The output ends with a summary of the test results.
    
             Tests completed in 3.47s
             Passed: 20 Failed: 1 Skipped: 0 Pending: 0 Inconclusive: 0
        
        However, because Pester uses Write-Host, it does not write to the output 
        stream (stdout), so there are no output objects to save in a variable or 
        redirect to a file. 
    
        To direct Pester to create custom objects, use its PassThru parameter. The 
        result is a single PSCustomObject with a TestResult property that one 
        TestResult custom object for each test in the test file.
    
        To save the custom objects to a file, use the OutputFile and OutputFormat 
        parameters of Invoke-Pester, which save the output in NUnitXml and 
        LegacyNUnitXml formats that are easy to parse and commonly used by reporting
        tools.
    
    
    
      REAL-WORLD EXAMPLES
        For help in writing Pester tests, examine the extensive collection of tests 
        that Pester uses to verify its Windows PowerShell code.
    
        To find the Pester tests in the Pester module directory, type:
    
            dir <Pester_module_path>\*Tests.ps1 -Recurse
    
           -or-
    
    	dir (Get-Module Pester -ListAvailable).ModuleBase -Include *Tests.ps1 -Recurse
    
    
    SEE ALSO
        Pester wiki: https://github.com/pester/pester/wiki
        Describe
        Context
        It
        New-Fixture
        Invoke-Pester
        about_Mocking
        about_Should
        about_TestDrive
  • tools\en-US\about_Should.help.txt Show
    TOPIC
        about_Should
    
    SHORT DESCRIPTION
        Provides assertion convenience methods for comparing objects and throwing
        test failures when test expectations fail.
    
    LONG DESCRIPTION
        Should is an Extension of System.Object and can be used as a native type
        inside Describe blocks. The various Should member methods can be invoked
        directly from an object being compared. It is typically used in individual
        It blocks to verify the results of an expectation. The Should method is
        typically called from the "actual" object being compared and takes the
        expected" object as a parameter. Should includes several members that
        perform various comparisons of objects and will throw a PesterFailure when
        the objects do not evaluate to be comparable.
    
      SHOULD MEMBERS
        Be
            Compares one object with another for equality and throws if the two
            objects are not the same.
    
            $actual="Actual value"
            $actual | Should Be "actual value" # Test will pass
            $actual | Should Be "not actual value"  # Test will fail
    
        BeExactly
            Compares one object with another for equality and throws if the two objects are not the same.  This comparison is case sensitive.
    
            $actual="Actual value"
            $actual | Should BeExactly "Actual value" # Test will pass
            $actual | Should BeExactly "actual value" # Test will fail
    
        BeGreaterThan
            Asserts that a number is greater than an expected value. Uses PowerShell's -gt operator to compare the two values.
    
            $Error.Count | Should BeGreaterThan 0
    
        BeIn
            Asserts that a collection of values contain a specific value. Uses PowerShell's -contains operator to confirm.
    
            1 | Should BeIn @(1,2,3,'a','b','c')
    
        BeLessThan
            Asserts that a number is less than an expected value. Uses PowerShell's -gt operator to compare the two values.
    
            $Error.Count | Should BeLessThan 1
    
        BeLike
            Asserts that the actual value matches a wildcard pattern using PowerShell's -like operator.  This comparison is not case-sensitive.
    
            $actual="Actual value"
            $actual | Should BeLike "actual *" # Test will pass
            $actual | Should BeLike "not actual *" # Test will fail
    
        BeLikeExactly
    
            Asserts that the actual value matches a wildcard pattern using PowerShell's -like operator.  This comparison is case-sensitive.
    
            $actual="Actual value"
            $actual | Should BeLikeExactly "Actual *" # Test will pass
            $actual | Should BeLikeExactly "actual *" # Test will fail
    
        BeOfType
            Asserts that the actual value should be an object of a specified type (or a subclass of the specified type) using PowerShell's -is operator:
    
            $actual = Get-Item $env:SystemRoot
            $actual | Should BeOfType System.IO.DirectoryInfo   # Test will pass; object is a DirectoryInfo
            $actual | Should BeOfType System.IO.FileSystemInfo  # Test will pass; DirectoryInfo base class is FileSystemInfo
    
            $actual | Should BeOfType System.IO.FileInfo        # Test will fail; FileInfo is not a base class of DirectoryInfo
    
        BeNullOrEmpty
            Checks values for null or empty (strings). The static [String]::IsNullOrEmpty() method is used to do the comparison.
    
            $null | Should BeNullOrEmpty # Test will pass
            $null | Should Not BeNullOrEmpty # Test will fail
            @()   | Should BeNullOrEmpty # Test will pass
            ""    | Should BeNullOrEmpty # Test will pass
        Exist
            Does not perform any comparison but checks if the object calling Exist
            is present in a PS Provider. The object must have valid path syntax. It
            essentially must pass a Test-Path call.
    
            $actual=(Dir . )[0].FullName
            Remove-Item $actual
            $actual | Should Exist # Test will fail
    
        Contain
            Checks to see if a file contains the specified text.  This search is not case sensitive and uses regular expressions.
    
            Set-Content -Path TestDrive:\file.txt -Value 'I am a file.'
            'TestDrive:\file.txt' | Should Contain 'I Am' # Test will pass
            'TestDrive:\file.txt' | Should Contain '^I.*file$' # Test will pass
    
            'TestDrive:\file.txt' | Should Contain 'I Am Not' # Test will fail
    
            Tip: Use [regex]::Escape("pattern") to match the exact text.
    
            Set-Content -Path TestDrive:\file.txt -Value 'I am a file.'
            'TestDrive:\file.txt' | Should Contain 'I.am.a.file' # Test will pass
            'TestDrive:\file.txt' | Should Contain ([regex]::Escape('I.am.a.file')) # Test will fail
    
        ContainExactly
            Checks to see if a file contains the specified text.  This search is case sensitive and uses regular expressions to match the text.
    
            Set-Content -Path TestDrive:\file.txt -Value 'I am a file.'
            'TestDrive:\file.txt' | Should Contain 'I am' # Test will pass
            'TestDrive:\file.txt' | Should Contain 'I Am' # Test will fail
    
        Match
            Uses a regular expression to compare two objects. This comparison is not case sensitive.
    
            "I am a value" | Should Match "I Am" # Test will pass
            "I am a value" | Should Match "I am a bad person" # Test will fail
    
            Tip: Use [regex]::Escape("pattern") to match the exact text.
    
            "Greg" | Should Match ".reg" # Test will pass
            "Greg" | Should Match ([regex]::Escape(".reg")) # Test will fail
    
        MatchExactly
            Uses a regular expression to compare two objects.  This comparison is case sensitive.
    
            "I am a value" | Should MatchExactly "I am" # Test will pass
            "I am a value" | Should MatchExactly "I Am" # Test will fail
    
        Throw
            Checks if an exception was thrown. Enclose input in a script block.
    
            { foo } | Should Throw # Test will pass
            { $foo = 1 } | Should Throw # Test will fail
            { foo } | Should Not Throw # Test will fail
            { $foo = 1 } | Should Not Throw # Test will pass
    
            Warning: The input object must be a ScriptBlock, otherwise it is processed outside of the assertion.
    
            Get-Process -Name "process" -ErrorAction Stop | Should Throw # Should pass, but the exception thrown by Get-Process causes the test to fail.
    
      NEGATIVE ASSERTIONS
        Any of the Should operators described above can be negated by using the word "Not" before the operator.  For example:
    
        'one' | Should Not Be 'Two'
        { Get-Item $env:SystemRoot } | Should Not Throw
    
      USING SHOULD IN A TEST
    
        function Add-Numbers($a, $b) {
            return $a + $b
        }
    
        Describe "Add-Numbers" {
    
            It "adds positive numbers" {
                $sum = Add-Numbers 2 3
                $sum | Should Be 3
            }
        }
    
        This test will fail since 3 will not be equal to the sum of 2 and 3.
    
    SEE ALSO
      Describe
      Context
      It
    
  • tools\en-US\about_TestDrive.help.txt Show
    TOPIC
        about_TestDrive
    
    SHORT DESCRIPTION
        A PSDrive for file activity limited to the scope of a singe Describe or 
        Context block.
    
    LONG DESCRIPTION	
        A test may need to work with file operations and validate certain types of 
        file activities. It is usually desirable not to perform file activity tests 
        that will produce side effects outside of an individual test. Pester 
        creates a PSDrive inside the user's temporary drive that is accessible via a 
        names PSDrive TestDrive:. Pester will remove this drive after the test 
        completes. You may use this drive to isolate the file operations of your 
        test to a temporary store.
    
    EXAMPLE
    	function Add-Footer($path, $footer) {
    	    Add-Content $path -Value $footer
    	}
    
    	Describe "Add-Footer" {
    	    $testPath="TestDrive:\test.txt"
    	    Set-Content $testPath -value "my test text."
    	    Add-Footer $testPath "-Footer"
    	    $result = Get-Content $testPath
    
    	    It "adds a footer" {
    	        (-join $result).Should.Be("my test text.-Footer")
    	    }
    	}
    	
    	When this test completes, the contents of the TestDrive PSDrive will 
    	be removed.
    
    SEE ALSO
        Context
        Describe
        It
        about_Should
    
  • tools\en-US\Gherkin.psd1 Show
    @{
        StartMessage = "Testing all features in '{0}'"
        FilterMessage = " for scenarios matching '{0}'"
        TagMessage = " with tags: '{0}'"
        MessageOfs = "', '"
    
        CoverageTitle   = "Code coverage report:"
        CoverageMessage = "Covered {2:P2} of {3:N0} analyzed {0} in {4:N0} {1}."
        MissedSingular  = 'Missed command:'
        MissedPlural    = 'Missed commands:'
        CommandSingular = 'Command'
        CommandPlural   = 'Commands'
        FileSingular    = 'File'
        FilePlural      = 'Files'
    
        Describe = "Feature: {0}"
        Context  = "Scenario: {0}"
        Margin   = "  "
        Timing   = "Testing completed in {0}"
        
        # If this is set to an empty string, the count won't be printed
        ContextsPassed    = "Scenarios Passed: {0} "
        ContextsFailed    = "Failed: {0}"
        TestsPassed       = "Steps Passed: {0} "
        TestsFailed       = "Failed: {0} "
        TestsSkipped      = 'Skipped: {0} '
        TestsPending      = 'Pending: {0} '
        TestsInconclusive = 'Inconclusive: {0} '
    }
  • tools\en-US\RSpec.psd1 Show
    @{
        StartMessage   = 'Executing all tests in {0}'
        FilterMessage  = ' matching test name {0}'
        TagMessage     = ' with Tags {0}'
        MessageOfs     = "', '"
    
        CoverageTitle   = 'Code coverage report:'
        CoverageMessage = 'Covered {2:P2} of {3:N0} analyzed {0} in {4:N0} {1}.'
        MissedSingular  = 'Missed command:'
        MissedPlural    = 'Missed commands:'
        CommandSingular = 'Command'
        CommandPlural   = 'Commands'
        FileSingular    = 'File'
        FilePlural      = 'Files'
    
        Describe = 'Describing {0}'
        Script   = 'Executing script {0}'
        Context  = 'Context {0}'
        Margin   = '  '
        Timing   = 'Tests completed in {0}'
    
        # If this is set to an empty string, the count won't be printed
        ContextsPassed = ''
        ContextsFailed = ''
    
        TestsPassed       = 'Tests Passed: {0} '
        TestsFailed       = 'Failed: {0} '
        TestsSkipped      = 'Skipped: {0} '
        TestsPending      = 'Pending: {0} '
        TestsInconclusive = 'Inconclusive: {0} '
    }
  • tools\Examples\Calculator\Add-Numbers.ps1 Show
    function Add-Numbers($a, $b) {
        return $a + $b
    }
    
  • tools\Examples\Calculator\Add-Numbers.Tests.ps1 Show
    $here = Split-Path -Parent $MyInvocation.MyCommand.Path
    . "$here\Add-Numbers.ps1"
    
    Describe -Tags "Example" "Add-Numbers" {
    
        It "adds positive numbers" {
            Add-Numbers 2 3 | Should Be 5
        }
    
        It "adds negative numbers" {
            Add-Numbers (-2) (-2) | Should Be (-4)
        }
    
        It "adds one negative number to positive number" {
            Add-Numbers (-2) 2 | Should Be 0
        }
    
        It "concatenates strings if given strings" {
            Add-Numbers two three | Should Be "twothree"
        }
    
        It "should not be 0" {
            Add-Numbers 2 3 | Should Not Be 0
        }
    }
    
    
  • tools\Examples\Validator\Validator.feature
  • tools\Examples\Validator\Validator.ps1 Show
    function MyValidator($thing_to_validate) {
        return $thing_to_validate.StartsWith("s")
    }
    
    function Invoke-SomethingThatUsesMyValidator {
        param(
            [ValidateScript({MyValidator $_})]
            $some_param
        )
    }
  • tools\Examples\Validator\Validator.Steps.ps1 Show
    $global:ValidatorRoot = Split-Path $MyInvocation.MyCommand.Path
    
    BeforeAllFeatures {
        New-Module -Name ValidatorTest {
            . $global:ValidatorRoot\Validator.ps1 -Verbose
        } | Import-Module -Scope Global
    }
    
    AfterAllFeatures {
        Remove-Module ValidatorTest
    }
    
    Given 'MyValidator is mocked to return True' {
        Mock MyValidator -Module ValidatorTest -MockWith { return $true }
    }
    
    When 'Someone calls something that uses MyValidator' {
        Invoke-SomethingThatUsesMyValidator "false"
    }
    
    Then 'MyValidator gets called once' {
        Assert-MockCalled -Module ValidatorTest MyValidator 1
    }
    
    Given 'MyValidator' {}
    
    When 'MyValidator is called with (?<word>\w+)' {
        param($word)
        $script:result = MyValidator $word
    }
    
    Then 'MyValidator should return (?<expected>\w+)' {
        param($expected)
        $expected = $expected -eq "true"
        $result | Should Be $expected
    }
    
  • tools\Examples\Validator\Validator.Tests.ps1 Show
    $scriptRoot = Split-Path $MyInvocation.MyCommand.Path
    . $scriptRoot\Validator.ps1 -Verbose
    
    Describe "Testing a validator" {
    
        It "calls MyValidator" {
            Mock MyValidator -MockWith { return $true }
            Invoke-SomethingThatUsesMyValidator "test"
            $was_called_once = 1
            Assert-MockCalled MyValidator $was_called_once
        }
    
    }
    
    Describe "MyValidator" {
    
        It "passes things that start with the letter S" {
            $result = MyValidator "summer"
            $result | Should Be $true
        }
    
        It "does not pass a param that does not start with S" {
            $result = MyValidator "bummer"
            $result | Should Be $false
        }
    }
    
    
  • tools\Functions\Assertions\Be.ps1 Show
    #Be
    function PesterBe($ActualValue, $ExpectedValue, [switch] $Negate) {
        [bool] $succeeded = ArraysAreEqual $ActualValue $ExpectedValue
    
        if ($Negate) { $succeeded = -not $succeeded }
    
        $failureMessage = ''
    
        if (-not $succeeded)
        {
            if ($Negate)
            {
                $failureMessage = NotPesterBeFailureMessage -ActualValue $ActualValue -Expected $ExpectedValue
            }
            else
            {
                $failureMessage = PesterBeFailureMessage -ActualValue $ActualValue -Expected $ExpectedValue
            }
        }
    
        return & $SafeCommands['New-Object'] psobject -Property @{
            Succeeded      = $succeeded
            FailureMessage = $failureMessage
        }
    }
    
    function PesterBeFailureMessage($ActualValue, $ExpectedValue) {
        # This looks odd; it's to unroll single-element arrays so the "-is [string]" expression works properly.
        $ActualValue = $($ActualValue)
        $ExpectedValue = $($ExpectedValue)
    
        if (-not (($ExpectedValue -is [string]) -and ($ActualValue -is [string])))
        {
            return "Expected: {$ExpectedValue}`nBut was:  {$ActualValue}"
        }
        <#joining the output strings to a single string here, otherwise I get
           Cannot find an overload for "Exception" and the argument count: "4".
           at line: 63 in C:\Users\nohwnd\github\pester\Functions\Assertions\Should.ps1
    
        This is a quickwin solution, doing the join in the Should directly might be better
        way of doing this. But I don't want to mix two problems.
        #>
        ( Get-CompareStringMessage -Expected $ExpectedValue -Actual $ActualValue ) -join "`n"
    }
    
    function NotPesterBeFailureMessage($ActualValue, $ExpectedValue) {
        return "Expected: value was {$ActualValue}, but should not have been the same"
    }
    
    Add-AssertionOperator -Name               Be `
                          -Test               $function:PesterBe `
                          -Alias              'EQ' `
                          -SupportsArrayInput
    
    #BeExactly
    function PesterBeExactly($ActualValue, $ExpectedValue) {
        [bool] $succeeded = ArraysAreEqual $ActualValue $ExpectedValue -CaseSensitive
    
        if ($Negate) { $succeeded = -not $succeeded }
    
        $failureMessage = ''
    
        if (-not $succeeded)
        {
            if ($Negate)
            {
                $failureMessage = NotPesterBeExactlyFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
            }
            else
            {
                $failureMessage = PesterBeExactlyFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
            }
        }
    
        return New-Object psobject -Property @{
            Succeeded      = $succeeded
            FailureMessage = $failureMessage
        }
    }
    
    function PesterBeExactlyFailureMessage($ActualValue, $ExpectedValue) {
        # This looks odd; it's to unroll single-element arrays so the "-is [string]" expression works properly.
        $ActualValue = $($ActualValue)
        $ExpectedValue = $($ExpectedValue)
    
        if (-not (($ExpectedValue -is [string]) -and ($ActualValue -is [string])))
        {
            return "Expected exactly: {$ExpectedValue}`nBut was: {$ActualValue}"
        }
        <#joining the output strings to a single string here, otherwise I get
           Cannot find an overload for "Exception" and the argument count: "4".
           at line: 63 in C:\Users\nohwnd\github\pester\Functions\Assertions\Should.ps1
    
        This is a quickwin solution, doing the join in the Should directly might be better
        way of doing this. But I don't want to mix two problems.
        #>
        ( Get-CompareStringMessage -Expected $ExpectedValue -Actual $ActualValue -CaseSensitive ) -join "`n"
    }
    
    function NotPesterBeExactlyFailureMessage($ActualValue, $ExpectedValue) {
        return "Expected: value was {$ActualValue}, but should not have been exactly the same"
    }
    
    Add-AssertionOperator -Name               BeExactly `
                          -Test               $function:PesterBeExactly `
                          -Alias              'CEQ' `
                          -SupportsArrayInput
    
    
    #common functions
    function Get-CompareStringMessage {
        param(
            [Parameter(Mandatory=$true)]
            [AllowEmptyString()]
            [String]$ExpectedValue,
            [Parameter(Mandatory=$true)]
            [AllowEmptyString()]
            [String]$Actual,
            [switch]$CaseSensitive
        )
    
        $ExpectedValueLength = $ExpectedValue.Length
        $actualLength = $actual.Length
        $maxLength = $ExpectedValueLength,$actualLength | & $SafeCommands['Sort-Object'] -Descending | & $SafeCommands['Select-Object'] -First 1
    
        $differenceIndex = $null
        for ($i = 0; $i -lt $maxLength -and ($null -eq $differenceIndex); ++$i){
            $differenceIndex = if ($CaseSensitive -and ($ExpectedValue[$i] -cne $actual[$i]))
            {
                $i
            }
            elseif ($ExpectedValue[$i] -ne $actual[$i])
            {
                $i
            }
        }
    
        [string]$output = $null
        if ($null -ne $differenceIndex)
        {
            if ($ExpectedValue.Length -ne $actual.Length) {
               "Expected string length $ExpectedValueLength but was $actualLength. Strings differ at index $differenceIndex."
            }
            else
            {
               "String lengths are both $ExpectedValueLength. Strings differ at index $differenceIndex."
            }
    
            "Expected: {{{0}}}" -f ( $ExpectedValue | Expand-SpecialCharacters )
            "But was:  {{{0}}}" -f ( $actual | Expand-SpecialCharacters )
    
            $specialCharacterOffset = $null
            if ($differenceIndex -ne 0)
            {
                #count all the special characters before the difference
                $specialCharacterOffset = ($actual[0..($differenceIndex-1)] |
                    & $SafeCommands['Where-Object'] {"`n","`r","`t","`b","`0" -contains $_} |
                    & $SafeCommands['Measure-Object'] |
                    & $SafeCommands['Select-Object'] -ExpandProperty Count)
            }
    
            '-'*($differenceIndex+$specialCharacterOffset+11)+'^'
        }
    }
    
    function Expand-SpecialCharacters {
        param (
        [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [AllowEmptyString()]
        [string[]]$InputObject)
        process {
            $InputObject -replace "`n","\n" -replace "`r","\r" -replace "`t","\t" -replace "`0", "\0" -replace "`b","\b"
        }
    }
    
    function ArraysAreEqual
    {
        param (
            [object[]] $First,
            [object[]] $Second,
            [switch] $CaseSensitive
        )
    
        # Do not remove the subexpression @() operators in the following two lines; doing so can cause a
        # silly error in PowerShell v3.  (Null Reference exception from the PowerShell engine in a
        # method called CheckAutomationNullInCommandArgumentArray(System.Object[]) ).
        $firstNullOrEmpty  = ArrayOrSingleElementIsNullOrEmpty -Array @($First)
        $secondNullOrEmpty = ArrayOrSingleElementIsNullOrEmpty -Array @($Second)
    
        if ($firstNullOrEmpty -or $secondNullOrEmpty)
        {
            return $firstNullOrEmpty -and $secondNullOrEmpty
        }
    
        if ($First.Count -ne $Second.Count) { return $false }
    
        for ($i = 0; $i -lt $First.Count; $i++)
        {
            if ((IsCollection $First[$i]) -or (IsCollection $Second[$i]))
            {
                if (-not (ArraysAreEqual -First $First[$i] -Second $Second[$i] -CaseSensitive:$CaseSensitive))
                {
                    return $false
                }
            }
            else
            {
                if ($CaseSensitive)
                {
                    $comparer = { $args[0] -ceq $args[1] }
                }
                else
                {
                    $comparer = { $args[0] -eq $args[1] }
                }
    
                if (-not (& $comparer $First[$i] $Second[$i]))
                {
                    return $false
                }
            }
        }
    
        return $true
    }
    
    function ArrayOrSingleElementIsNullOrEmpty
    {
        param ([object[]] $Array)
    
        return $null -eq $Array -or $Array.Count -eq 0 -or ($Array.Count -eq 1 -and $null -eq $Array[0])
    }
    
    function IsCollection
    {
        param ([object] $InputObject)
    
        return $InputObject -is [System.Collections.IEnumerable] -and
               $InputObject -isnot [string] -and
               $InputObject -isnot [System.Collections.IDictionary]
    }
    
    function ReplaceValueInArray
    {
        param (
            [object[]] $Array,
            [object] $Value,
            [object] $NewValue
        )
    
        foreach ($object in $Array)
        {
            if ($Value -eq $object)
            {
                $NewValue
            }
            elseif (@($object).Count -gt 1)
            {
                ReplaceValueInArray -Array @($object) -Value $Value -NewValue $NewValue
            }
            else
            {
                $object
            }
        }
    }
    
    
  • tools\Functions\Assertions\Be.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "PesterBe" {
            It "returns true if the 2 arguments are equal" {
                1 | Should Be 1
                1 | Should -Be 1
                1 | Should -EQ 1
            }
            It "returns true if the 2 arguments are equal and have different case" {
                'A' | Should Be 'a'
                'A' | Should -Be 'a'
                'A' | Should -EQ 'a'
            }
    
            It "returns false if the 2 arguments are not equal" {
                1 | Should Not Be 2
                1 | Should -Not -Be 2
                1 | Should -Not -EQ 2
            }
    
            It 'Compares Arrays properly' {
                $array = @(1,2,3,4,'I am a string', (New-Object psobject -Property @{ IAm = 'An Object' }))
                $array | Should Be $array
                $array | Should -Be $array
                $array | Should -EQ $array
            }
    
            It 'Compares arrays with correct case-insensitive behavior' {
                $string = 'I am a string'
                $array = @(1,2,3,4,$string)
                $arrayWithCaps = @(1,2,3,4,$string.ToUpper())
    
                $array | Should Be $arrayWithCaps
                $array | Should -Be $arrayWithCaps
                $array | Should -EQ $arrayWithCaps
            }
    
            It 'Handles reference types properly' {
                $object1 = New-Object psobject -Property @{ Value = 'Test' }
                $object2 = New-Object psobject -Property @{ Value = 'Test' }
    
                $object1 | Should Be $object1
                $object1 | Should Not Be $object2
                $object1 | Should -Be $object1
                $object1 | Should -Not -Be $object2
                $object1 | Should -EQ $object1
                $object1 | Should -Not -EQ $object2
            }
    
            It 'Handles arrays with nested arrays' {
                $array1 = @(
                    @(1,2,3,4,5),
                    @(6,7,8,9,0)
                )
    
                $array2 = @(
                    @(1,2,3,4,5),
                    @(6,7,8,9,0)
                )
    
                $array1 | Should Be $array2
                $array1 | Should -Be $array2
                $array1 | Should -EQ $array2
    
                $array3 = @(
                    @(1,2,3,4,5),
                    @(6,7,8,9,0, 'Oops!')
                )
    
                $array1 | Should Not Be $array3
                $array1 | Should -Not -Be $array3
                $array1 | Should -Not -EQ $array3
            }
        }
    
        Describe "PesterBeFailureMessage" {
            #the correctness of difference index value and the arrow pointing to the correct place
            #are not tested here thoroughly, but the behaviour was visually checked and is
            #implicitly tested by using the whole output in the following tests
    
    
            It "Returns nothing for two identical strings" {
                #this situation should actually never happen, as the code is called
                #only when the objects are not equal
    
                $string = "string"
                PesterBeFailureMessage $string $string | Should BeNullOrEmpty
                PesterBeFailureMessage $string $string | Should -BeNullOrEmpty
            }
    
            It "Outputs less verbose message for two different objects that are not strings" {
                PesterBeFailureMessage 2 1 | Should Be "Expected: {1}`nBut was:  {2}"
                PesterBeFailureMessage 2 1 | Should -Be "Expected: {1}`nBut was:  {2}"
            }
    
            It "Outputs verbose message for two strings of different length" {
                PesterBeFailureMessage "actual" "expected" | Should Be "Expected string length 8 but was 6. Strings differ at index 0.`nExpected: {expected}`nBut was:  {actual}`n-----------^"
                PesterBeFailureMessage "actual" "expected" | Should -Be "Expected string length 8 but was 6. Strings differ at index 0.`nExpected: {expected}`nBut was:  {actual}`n-----------^"
            }
    
            It "Outputs verbose message for two different strings of the same length" {
                PesterBeFailureMessage "x" "y" | Should Be "String lengths are both 1. Strings differ at index 0.`nExpected: {y}`nBut was:  {x}`n-----------^"
                PesterBeFailureMessage "x" "y" | Should -Be "String lengths are both 1. Strings differ at index 0.`nExpected: {y}`nBut was:  {x}`n-----------^"
            }
    
            It "Replaces non-printable characters correctly" {
                PesterBeFailureMessage "`n`r`b`0`tx" "`n`r`b`0`ty" | Should Be "String lengths are both 6. Strings differ at index 5.`nExpected: {\n\r\b\0\ty}`nBut was:  {\n\r\b\0\tx}`n---------------------^"
                PesterBeFailureMessage "`n`r`b`0`tx" "`n`r`b`0`ty" | Should -Be "String lengths are both 6. Strings differ at index 5.`nExpected: {\n\r\b\0\ty}`nBut was:  {\n\r\b\0\tx}`n---------------------^"
            }
    
            It "The arrow points to the correct position when non-printable characters are replaced before the difference" {
                PesterBeFailureMessage "123`n456" "123`n789" | Should Be "String lengths are both 7. Strings differ at index 4.`nExpected: {123\n789}`nBut was:  {123\n456}`n----------------^"
                PesterBeFailureMessage "123`n456" "123`n789" | Should -Be "String lengths are both 7. Strings differ at index 4.`nExpected: {123\n789}`nBut was:  {123\n456}`n----------------^"
            }
    
            It "The arrow points to the correct position when non-printable characters are replaced after the difference" {
                PesterBeFailureMessage "abcd`n123" "abc!`n123" | Should Be "String lengths are both 8. Strings differ at index 3.`nExpected: {abc!\n123}`nBut was:  {abcd\n123}`n--------------^"
                PesterBeFailureMessage "abcd`n123" "abc!`n123" | Should -Be "String lengths are both 8. Strings differ at index 3.`nExpected: {abc!\n123}`nBut was:  {abcd\n123}`n--------------^"
            }
        }
    }
    
    InModuleScope Pester {
        Describe "BeExactly" {
            It "passes if letter case matches" {
                'a' | Should BeExactly 'a'
                'a' | Should -BeExactly 'a'
            }
    
            It "fails if letter case doesn't match" {
                'A' | Should Not BeExactly 'a'
                'A' | Should -Not -BeExactly 'a'
            }
    
            It "passes for numbers" {
                1 | Should BeExactly 1
                2.15 | Should BeExactly 2.15
                1 | Should -BeExactly 1
                2.15 | Should -BeExactly 2.15
            }
    
            It 'Compares Arrays properly' {
                $array = @(1,2,3,4,'I am a string', (New-Object psobject -Property @{ IAm = 'An Object' }))
                $array | Should BeExactly $array
                $array | Should -BeExactly $array
            }
    
            It 'Compares arrays with correct case-sensitive behavior' {
                $string = 'I am a string'
                $array = @(1,2,3,4,$string)
                $arrayWithCaps = @(1,2,3,4,$string.ToUpper())
    
                $array | Should Not BeExactly $arrayWithCaps
                $array | Should -Not -BeExactly $arrayWithCaps
            }
        }
    
        Describe "PesterBeExactlyFailureMessage" {
            It "Writes verbose message for strings that differ by case" {
                PesterBeExactlyFailureMessage "a" "A" | Should Be "String lengths are both 1. Strings differ at index 0.`nExpected: {A}`nBut was:  {a}`n-----------^"
                PesterBeExactlyFailureMessage "a" "A" | Should -Be "String lengths are both 1. Strings differ at index 0.`nExpected: {A}`nBut was:  {a}`n-----------^"
            }
        }
    }
    
  • tools\Functions\Assertions\BeGreaterThan.ps1 Show
    function PesterBeGreaterThan($ActualValue, $ExpectedValue, [switch] $Negate)
    {
        [bool] $succeeded = $ActualValue -gt $ExpectedValue
        if ($Negate) { $succeeded = -not $succeeded }
    
        $failureMessage = ''
    
        if (-not $succeeded)
        {
            if ($Negate)
            {
                $failureMessage = NotPesterBeGreaterThanFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
            }
            else
            {
                $failureMessage = PesterBeGreaterThanFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
            }
        }
    
        return New-Object psobject -Property @{
            Succeeded      = $succeeded
            FailureMessage = $failureMessage
        }
    }
    
    function PesterBeGreaterThanFailureMessage($ActualValue,$ExpectedValue)
    {
        return "Expected {$ActualValue} to be greater than {$ExpectedValue}"
    }
    
    function NotPesterBeGreaterThanFailureMessage($ActualValue,$ExpectedValue)
    {
        return "Expected {$ActualValue} to be less than or equal to {$ExpectedValue}"
    }
    
    Add-AssertionOperator -Name  BeGreaterThan `
                          -Test  $function:PesterBeGreaterThan `
                          -Alias 'GT'
    
  • tools\Functions\Assertions\BeGreaterThan.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "PesterBeGreaterThan" {
            It "passes if value greater than expected" {
                2 | Should BeGreaterThan 1
                2 | Should -BeGreaterThan 1
                2 | Should -GT 1
            }
    
            It "fails if values equal" {
                3 | Should Not BeGreaterThan 3
                3 | Should -Not -BeGreaterThan 3
                3 | Should -Not -GT 3
            }
    
            It "fails if value less than expected" {
                4 | Should Not BeGreaterThan 5
                4 | Should -Not -BeGreaterThan 5
                4 | Should -Not -GT 5
            }
        }
    
    }
    
  • tools\Functions\Assertions\BeIn.ps1 Show
    function PesterBeIn($ActualValue, $ExpectedValue, [switch] $Negate)
    {
        [bool] $succeeded = $ExpectedValue -contains $ActualValue
        if ($Negate) { $succeeded = -not $succeeded }
    
        $failureMessage = ''
    
        if (-not $succeeded)
        {
            if ($Negate)
            {
                $failureMessage = NotPesterBeInFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
            }
            else
            {
                $failureMessage = PesterBeInFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
            }
        }
    
        return New-Object psobject -Property @{
            Succeeded      = $succeeded
            FailureMessage = $failureMessage
        }
    }
    
    function PesterBeInFailureMessage($ActualValue, $ExpectedValue) {
        if(-not ([bool]($ExpectedValue -contains $ActualValue))) {
            return "Expected: ${ActualValue} to be in collection [$($ExpectedValue -join ',')] but was not found."
        }
    }
    
    function NotPesterBeInFailureMessage($ActualValue, $ExpectedValue) {
        if([bool]($ExpectedValue -contains $ActualValue)) {
            return "Expected: ${ActualValue} to not be in collection [$($ExpectedValue -join ',')] but was found."
        }
    }
    
    Add-AssertionOperator -Name BeIn -Test $function:PesterBeIn
    
  • tools\Functions\Assertions\BeIn.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "PesterBeIn" {
            It "passes if value is in the collection" {
                PesterBeIn 1 (@(1,2,3))
                PesterBeIn 'a' (@(1,'a',3))
                1 | Should BeIn @(1,2,3)
                'a' | Should -BeIn @(1,'a',3)
            }
            It "fails if value is not in the collection" {
                PesterBeIn 4 (@(1,2,3))
                PesterBeIn 'b' (@(1,'a',3))
                4 | Should Not BeIn @(1,2,3)
                'b' | Should -Not -BeIn @(1,'a',3)
            }
        }
    }
    
  • tools\Functions\Assertions\BeLessThan.ps1 Show
    function PesterBeLessThan($ActualValue, $ExpectedValue)
    {
        [bool] $succeeded = $ActualValue -lt $ExpectedValue
        if ($Negate) { $succeeded = -not $succeeded }
    
        $failureMessage = ''
    
        if (-not $succeeded)
        {
            if ($Negate)
            {
                $failureMessage = NotPesterBeLessThanFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
            }
            else
            {
                $failureMessage = PesterBeLessThanFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
            }
        }
    
        return New-Object psobject -Property @{
            Succeeded      = $succeeded
            FailureMessage = $failureMessage
        }
    }
    
    function PesterBeLessThanFailureMessage($ActualValue,$ExpectedValue)
    {
        return "Expected {$ActualValue} to be less than {$ExpectedValue}"
    }
    
    function NotPesterBeLessThanFailureMessage($ActualValue,$ExpectedValue)
    {
        return "Expected {$ActualValue} to be greater than or equal to {$ExpectedValue}"
    }
    
    Add-AssertionOperator -Name  BeLessThan `
                          -Test  $function:PesterBeLessThan `
                          -Alias 'LT'
    
  • tools\Functions\Assertions\BeLessThan.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "PesterBeLessThan" {
            It "passes if value Less than expected" {
                1 | Should BeLessThan 2
                1 | Should -BeLessThan 2
                1 | Should -LT 2
            }
    
            It "fails if values equal" {
                3 | Should Not BeLessthan 3
                3 | Should -Not -BeLessThan 3
                3 | Should -Not -LT 3
            }
    
            It "fails if value greater than expected" {
                5 | Should Not BeLessthan 4
                5 | Should -Not -BeLessThan 4
                5 | Should -Not -LT 4
            }
        }
    }
    
  • tools\Functions\Assertions\BeLike.ps1 Show
    function PesterBeLike($ActualValue, $ExpectedValue, [switch] $Negate)
    {
        [bool] $succeeded = $ActualValue -like $ExpectedValue
        if ($Negate) { $succeeded = -not $succeeded }
    
        $failureMessage = ''
    
        if (-not $succeeded)
        {
            if ($Negate)
            {
                $failureMessage = NotPesterBeLikeFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
            }
            else
            {
                $failureMessage = PesterBeLikeFailureMessage -ActualValue $ActualValue -ExpectedValue $ExpectedValue
            }
        }
    
        return New-Object psobject -Property @{
            Succeeded      = $succeeded
            FailureMessage = $failureMessage
        }
    }
    
    function PesterBeLikeFailureMessage($ActualValue, $ExpectedValue) {
        return "Expected: {$ActualValue} to be like the wildcard {$ExpectedValue}"
    }
    
    function NotPesterBeLikeFailureMessage($ActualValue, $ExpectedValue) {
        return "Expected: ${ActualValue} to not be like the wildcard ${ExpectedValue}"
    }
    
    Add-AssertionOperator -Name BeLike -Test  $function:PesterBeLike
    
  • tools\Functions\Assertions\BeLike.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "BeLike" {
            It "returns true for things that are like wildcard" {
    
                "foobar" | Should BeLike "*ob*"
                "foobar" | Should -BeLike "*ob*"
            }
    
            It "returns false for things that do not match" {
                { "foobar" | Should BeLike "oob" } | Should Throw
            }
    
            It "passes for strings with different case" {
                "foobar" | Should BeLike "FOOBAR"
            }
        }
    }
    
  • tools\Functions\Assertions\BeLikeExactly.ps1 Show
    function PesterBeLikeExactly($value, $expectedMatch) {
        return ($value -clike $expectedMatch)
    }
    
    function PesterBeLikeExactlyFailureMessage($value, $expectedMatch) {
        return "Expected: {$value} to be exactly like the wildcard {$expectedMatch}"
    }
    
    function NotPesterBeLikeExactlyFailureMessage($value, $expectedMatch) {
        return "Expected: ${value} to not be exactly like the wildcard ${expectedMatch}"
    }
    
    
  • tools\Functions\Assertions\BeLikeExactly.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "BeLike" {
            It "returns true for things that are like wildcard" {
                PesterBeLikeExactly "FOOBAR" "*OB*" | Should Be $true
            }
    
            It "returns false for things that do not match" {
                PesterBeLikeExactly "foobar" "oob" | Should Be $false
            }
    
            It "fails for strings with different case" {
                PesterBeLikeExactly "foobar" "*OB*" | Should Be $false
            }
        }
    }
    
  • tools\Functions\Assertions\BeNullOrEmpty.ps1 Show
    function PesterBeNullOrEmpty([object[]] $ActualValue, [switch] $Negate) {
        if ($null -eq $ActualValue -or $ActualValue.Count -eq 0)
        {
            $succeeded = $true
        }
        elseif ($ActualValue.Count -eq 1)
        {
            $succeeded = [String]::IsNullOrEmpty($ActualValue[0])
        }
        else
        {
            $succeeded = $false
        }
    
        if ($Negate) { $succeeded = -not $succeeded }
    
        $failureMessage = ''
    
        if (-not $succeeded)
        {
            if ($Negate)
            {
                $failureMessage = NotPesterBeNullOrEmptyFailureMessage -ActualValue $ActualValue
            }
            else
            {
                $failureMessage = PesterBeNullOrEmptyFailureMessage -ActualValue $ActualValue
            }
        }
    
        return New-Object psobject -Property @{
            Succeeded      = $succeeded
            FailureMessage = $failureMessage
        }
    }
    
    function PesterBeNullOrEmptyFailureMessage($ActualValue) {
        return "Expected: value to be empty but it was {$ActualValue}"
    }
    
    function NotPesterBeNullOrEmptyFailureMessage {
        return "Expected: value to not be empty"
    }
    
    Add-AssertionOperator -Name               BeNullOrEmpty `
                          -Test               $function:PesterBeNullOrEmpty `
                          -SupportsArrayInput
    
  • tools\Functions\Assertions\BeNullOrEmpty.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "PesterBeNullOrEmpty" {
            It "should return true if null" {
                $null | Should BeNullOrEmpty
                $null | Should -BeNullOrEmpty
            }
    
            It "should return true if empty string" {
                '' | Should BeNullOrEmpty
                '' | Should -BeNullOrEmpty
            }
    
            It "should return true if empty array" {
                @() | Should BeNullOrEmpty
                @() | Should -BeNullOrEmpty
            }
    
            It 'Should return false for non-empty strings or arrays' {
                'String' | Should Not BeNullOrEmpty
                1..5 | Should Not BeNullOrEmpty
                ($null,$null) | Should Not BeNullOrEmpty
                'String' | Should -Not -BeNullOrEmpty
                1..5 | Should -Not -BeNullOrEmpty
                ($null,$null) | Should -Not -BeNullOrEmpty
            }
        }
    }
    
  • tools\Functions\Assertions\BeOfType.ps1 Show
    function PesterBeOfType($ActualValue, $ExpectedType, [switch] $Negate) {
        $hash = @{ Succeeded = $true }
    
        trap [System.Management.Automation.PSInvalidCastException] { $hash['Succeeded'] = $false; continue }
    
        if($ExpectedType -is [string] -and !($ExpectedType -as [Type])) {
            $ExpectedType = $ExpectedType -replace '^\[(.*)\]$','$1'
        }
    
        $hash['Succeeded'] = $ActualValue -is $ExpectedType
    
        if ($Negate) { $hash['Succeeded'] = -not $hash['Succeeded'] }
    
        $failureMessage = ''
    
        if (-not $hash['Succeeded'])
        {
            if ($Negate)
            {
                $failureMessage = NotPesterBeOfTypeFailureMessage -ActualValue $ActualValue -ExpectedType $ExpectedType
            }
            else
            {
                $failureMessage = PesterBeOfTypeFailureMessage -ActualValue $ActualValue -ExpectedType $ExpectedType
            }
        }
    
        return New-Object psobject -Property @{
            Succeeded      = $hash['Succeeded']
            FailureMessage = $failureMessage
        }
    }
    
    function PesterBeOfTypeFailureMessage($ActualValue, $ExpectedType) {
        if($ExpectedType -is [string] -and !($ExpectedType -as [Type])) {
            $ExpectedType = $ExpectedType -replace '^\[(.*)\]$','$1'
        }
    
        if($Type = $ExpectedType -as [type]) {
            return "Expected: {$ActualValue} to be of type [$Type]"
        } else {
            return "Expected: {$ActualValue} to be of type [$ExpectedType], but unable to find type [$ExpectedType]. Make sure that the assembly that contains that type is loaded."
        }
    }
    
    function NotPesterBeOfTypeFailureMessage($ActualValue, $ExpectedType) {
        if($ExpectedType -is [string] -and -not $ExpectedType -as [Type]) {
            $ExpectedType = $ExpectedType -replace '^\[(.*)\]$','$1'
        }
        if($Type = $ExpectedType -as [type]) {
            return "Expected: {$ActualValue} to be of any type except [${Type}], but it's a [${Type}]"
        } else {
            return "Expected: {$ActualValue} to be of any type except [$ExpectedType], but unable to find type [$ExpectedType]. Make sure that the assembly that contains that type is loaded."
        }
    }
    
    Add-AssertionOperator -Name BeOfType `
                          -Test $function:PesterBeOfType
    
  • tools\Functions\Assertions\BeOfType.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "PesterBeOfType" {
            It "passes if value is of the expected type" {
                1 | Should BeOfType Int
                2.0 | Should BeOfType ([double])
                1 | Should -BeOfType Int
                2.0 | Should -BeOfType ([double])
            }
            It "fails if value is of a different types" {
                2 | Should Not BeOfType double
                2.0 | Should Not BeOfType ([string])
                2 | Should -Not -BeOfType double
                2.0 | Should -Not -BeOfType ([string])
            }
    
            It "fails if type isn't a type" {
                5 | Should Not BeOfType NotAType
                5 | Should -Not -BeOfType NotAType
            }
        }
    }
    
  • tools\Functions\Assertions\Contain.ps1 Show
    function PesterContain($ActualValue, $ExpectedContent, [switch] $Negate) {
        $succeeded = (@(& $SafeCommands['Get-Content'] -Encoding UTF8 $ActualValue) -match $ExpectedContent).Count -gt 0
    
        if ($Negate) { $succeeded = -not $succeeded }
    
        $failureMessage = ''
    
        if (-not $succeeded)
        {
            if ($Negate)
            {
                $failureMessage = NotPesterContainFailureMessage -ActualValue $ActualValue -ExpectedContent $ExpectedContent
            }
            else
            {
                $failureMessage = PesterContainFailureMessage -ActualValue $ActualValue -ExpectedContent $ExpectedContent
            }
        }
    
        return & $SafeCommands['New-Object'] psobject -Property @{
            Succeeded      = $succeeded
            FailureMessage = $failureMessage
        }
    }
    
    function PesterContainFailureMessage($ActualValue, $ExpectedContent) {
        return "Expected: file {$ActualValue} to contain {$ExpectedContent}"
    }
    
    function NotPesterContainFailureMessage($ActualValue, $ExpectedContent) {
        return "Expected: file {$ActualValue} to not contain {$ExpectedContent} but it did"
    }
    
    Add-AssertionOperator -Name Contain `
                          -Test $function:PesterContain
    
  • tools\Functions\Assertions\Contain.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "PesterContain" {
            Context "when testing file contents" {
                Setup -File "test.txt" "this is line 1`nrush is awesome`nAnd this is Unicode: ☺"
    
                It "returns true if the file contains the specified content" {
                    "$TestDrive\test.txt" | Should Contain rush
                    "$TestDrive\test.txt" | Should -Contain rush
                }
    
                It "returns true if the file contains the specified content with different case" {
                    "$TestDrive\test.txt" | Should Contain RUSH
                    "$TestDrive\test.txt" | Should -Contain RUSH
                }
    
                It "returns false if the file does not contain the specified content" {
                    "$TestDrive\test.txt" | Should Not Contain slime
                    "$TestDrive\test.txt" | Should -Not -Contain slime
                }
    
                It "returns true if the file contains the specified UTF8 content" {
                    "$TestDrive\test.txt" | Should Contain "☺"
                    "$TestDrive\test.txt" | Should -Contain "☺"
                }
            }
        }
    }
    
  • tools\Functions\Assertions\ContainExactly.ps1 Show
    function PesterContainExactly($ActualValue, $ExpectedContent, [switch] $Negate) {
        $succeeded = (@(& $SafeCommands['Get-Content'] -Encoding UTF8 $ActualValue) -cmatch $ExpectedContent).Count -gt 0
    
        if ($Negate) { $succeeded = -not $succeeded }
    
        $failureMessage = ''
    
        if (-not $succeeded)
        {
            if ($Negate)
            {
                $failureMessage = NotPesterContainExactlyFailureMessage -ActualValue $ActualValue -ExpectedContent $ExpectedContent
            }
            else
            {
                $failureMessage = PesterContainExactlyFailureMessage -ActualValue $ActualValue -ExpectedContent $ExpectedContent
            }
        }
    
        return & $SafeCommands['New-Object'] psobject -Property @{
            Succeeded      = $succeeded
            FailureMessage = $failureMessage
        }
    }
    
    function PesterContainExactlyFailureMessage($ActualValue, $ExpectedContent) {
        return "Expected: file {$ActualValue} to contain exactly {$ExpectedContent}"
    }
    
    function NotPesterContainExactlyFailureMessage($ActualValue, $ExpectedContent) {
        return "Expected: file {$ActualValue} to not contain exactly {$ExpectedContent} but it did"
    }
    
    Add-AssertionOperator -Name ContainExactly `
                          -Test $function:PesterContainExactly
    
  • tools\Functions\Assertions\ContainExactly.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "PesterContainExactly" {
            Context "when testing file contents" {
                Setup -File "test.txt" "this is line 1`nPester is awesome`nAnd this is Unicode: ☺"
                It "returns true if the file contains the specified content exactly" {
                    "$TestDrive\test.txt" | Should ContainExactly Pester
                    "$TestDrive\test.txt" | Should -ContainExactly Pester
                }
    
                It "returns false if the file does not contain the specified content exactly" {
                    "$TestDrive\test.txt" | Should Not ContainExactly pESTER
                    "$TestDrive\test.txt" | Should -Not -ContainExactly pESTER
                }
    
                It "returns true if the file contains the specified Unicode content exactyle" {
                    "$TestDrive\test.txt" | Should ContainExactly "☺"
                    "$TestDrive\test.txt" | Should -ContainExactly "☺"
                }
            }
        }
    }
    
  • tools\Functions\Assertions\ContainMultiline.ps1 Show
    function PesterContainMultiline($ActualValue, $ExpectedContent, [switch] $Negate) {
        $succeeded = [bool] ((Get-Content $ActualValue -delim ([char]0)) -match $ExpectedContent)
    
        if ($Negate) { $succeeded = -not $succeeded }
    
        $failureMessage = ''
    
        if (-not $succeeded)
        {
            if ($Negate)
            {
                $failureMessage = NotPesterContainMultilineFailureMessage -ActualValue $ActualValue -ExpectedContent $ExpectedContent
            }
            else
            {
                $failureMessage = PesterContainMultilineFailureMessage -ActualValue $ActualValue -ExpectedContent $ExpectedContent
            }
        }
    
        return New-Object psobject -Property @{
            Succeeded      = $succeeded
            FailureMessage = $failureMessage
        }
    }
    
    function PesterContainMultilineFailureMessage($ActualValue, $ExpectedContent) {
        return "Expected: file {$ActualValue} to contain {$ExpectedContent}"
    }
    
    function NotPesterContainMultilineFailureMessage($ActualValue, $ExpectedContent) {
        return "Expected: file {$ActualValue} to not contain {$ExpectedContent} but it did"
    }
    
    Add-AssertionOperator -Name  ContainMultiline `
                          -Test  $function:PesterContainMultiline
    
  • tools\Functions\Assertions\ContainMultiline.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "PesterContainMultiline" {
            Context "when testing file contents" {
                Setup -File "test.txt" "this is line 1`nthis is line 2`nPester is awesome"
                It "returns true if the file matches the specified content on one line" {
                    "$TestDrive\test.txt" | Should ContainMultiline  "Pester"
                }
    
                It "returns true if the file matches the specified content across multiple lines" {
                    "$TestDrive\test.txt" | Should ContainMultiline  "line 2`nPester"
                }
    
                It "returns false if the file does not contain the specified content" {
                    "$TestDrive\test.txt" | Should Not ContainMultiline  "Pastor"
                }
            }
        }
    }
    
  • tools\Functions\Assertions\Exist.ps1 Show
    function PesterExist($ActualValue, [switch] $Negate) {
        [bool] $succeeded = & $SafeCommands['Test-Path'] $ActualValue
    
        if ($Negate) { $succeeded = -not $succeeded }
    
        $failureMessage = ''
    
        if (-not $succeeded)
        {
            if ($Negate)
            {
                $failureMessage = NotPesterExistFailureMessage -ActualValue $ActualValue
            }
            else
            {
                $failureMessage = PesterExistFailureMessage -ActualValue $ActualValue
            }
        }
    
        return & $SafeCommands['New-Object'] psobject -Property @{
            Succeeded      = $succeeded
            FailureMessage = $failureMessage
        }
    }
    
    function PesterExistFailureMessage($ActualValue) {
        return "Expected: {$ActualValue} to exist"
    }
    
    function NotPesterExistFailureMessage($ActualValue) {
        return "Expected: {$ActualValue} to not exist, but it was found"
    }
    
    Add-AssertionOperator -Name Exist `
                          -Test $function:PesterExist
    
  • tools\Functions\Assertions\Exist.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "PesterExist" {
            It "returns true for paths that exist" {
                $TestDrive | Should Exist
                $TestDrive | Should -Exist
            }
    
            It "returns false for paths do not exist" {
                "$TestDrive\nonexistant" | Should Not Exist
                "$TestDrive\nonexistant" | Should -Not -Exist
            }
    
            It 'works for path with escaped [ ] characters' {
                New-Item -Path "TestDrive:\[test].txt" -ItemType File | Out-Null
                "TestDrive:\``[test``].txt"  | Should Exist
                "TestDrive:\``[test``].txt"  | Should -Exist
            }
    
            It 'returns correct result for function drive' {
                function f1 {}
    
                'function:f1' | Should Exist
                'function:f1' | Should -Exist
            }
    
            It 'returns correct result for env drive' {
                $env:test = 'somevalue'
    
                'env:test' | Should Exist
                'env:test' | Should -Exist
            }
    
            It 'returns correct result for env drive' {
                $env:test = 'somevalue'
    
                'env:test' | Should Exist
                'env:test' | Should -Exist
            }
        }
    }
    
  • tools\Functions\Assertions\Match.ps1 Show
    function PesterMatch($ActualValue, $RegularExpression, [switch] $Negate) {
        [bool] $succeeded = $ActualValue -match $RegularExpression
    
        if ($Negate) { $succeeded = -not $succeeded }
    
        $failureMessage = ''
    
        if (-not $succeeded)
        {
            if ($Negate)
            {
                $failureMessage = NotPesterMatchFailureMessage -ActualValue $ActualValue -RegularExpression $RegularExpression
            }
            else
            {
                $failureMessage = PesterMatchFailureMessage -ActualValue $ActualValue -RegularExpression $RegularExpression
            }
        }
    
        return New-Object psobject -Property @{
            Succeeded      = $succeeded
            FailureMessage = $failureMessage
        }
    }
    
    function PesterMatchFailureMessage($ActualValue, $RegularExpression) {
        return "Expected: {$ActualValue} to match the expression {$RegularExpression}"
    }
    
    function NotPesterMatchFailureMessage($ActualValue, $RegularExpression) {
        return "Expected: {$ActualValue} to not match the expression {$RegularExpression}"
    }
    
    Add-AssertionOperator -Name Match `
                          -Test $function:PesterMatch
    
  • tools\Functions\Assertions\Match.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "Match" {
            It "returns true for things that match" {
                'foobar' | Should Match 'ob'
                'foobar' | Should -Match 'ob'
            }
    
            It "returns false for things that do not match" {
                'foobar' | Should Not Match 'slime'
                'foobar' | Should -Not -Match 'slime'
            }
    
            It "passes for strings with different case" {
                'foobar' | Should Match 'FOOBAR'
                'foobar' | Should -Match 'FOOBAR'
            }
    
            It "uses regular expressions" {
                'foobar' | Should Match '\S{6}'
                'foobar' | Should -Match '\S{6}'
            }
        }
    }
    
  • tools\Functions\Assertions\MatchExactly.ps1 Show
    function PesterMatchExactly($ActualValue, $RegularExpression, [switch] $Negate) {
        [bool] $succeeded = $ActualValue -cmatch $RegularExpression
    
        if ($Negate) { $succeeded = -not $succeeded }
    
        $failureMessage = ''
    
        if (-not $succeeded)
        {
            if ($Negate)
            {
                $failureMessage = NotPesterMatchExactlyFailureMessage -ActualValue $ActualValue -RegularExpression $RegularExpression
            }
            else
            {
                $failureMessage = PesterMatchExactlyFailureMessage -ActualValue $ActualValue -RegularExpression $RegularExpression
            }
        }
    
        return New-Object psobject -Property @{
            Succeeded      = $succeeded
            FailureMessage = $failureMessage
        }
    }
    
    function PesterMatchExactlyFailureMessage($ActualValue, $RegularExpression) {
        return "Expected: {$ActualValue} to exactly match the expression {$RegularExpression}"
    }
    
    function NotPesterMatchExactlyFailureMessage($ActualValue, $RegularExpression) {
        return "Expected: {$ActualValue} to not match the expression {$RegularExpression} exactly"
    }
    
    Add-AssertionOperator -Name  MatchExactly `
                          -Test  $function:PesterMatchExactly `
                          -Alias 'CMATCH'
    
  • tools\Functions\Assertions\MatchExactly.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "MatchExactly" {
            It "returns true for things that match exactly" {
                'foobar' | Should MatchExactly 'ob'
                'foobar' | Should -MatchExactly 'ob'
                'foobar' | Should -CMATCH 'ob'
            }
    
            It "returns false for things that do not match exactly" {
                'foobar' | Should Not MatchExactly 'FOOBAR'
                'foobar' | Should -Not -MatchExactly 'FOOBAR'
                'foobar' | Should -Not -CMATCH 'FOOBAR'
            }
    
            It "uses regular expressions" {
                'foobar' | Should MatchExactly '\S{6}'
                'foobar' | Should -MatchExactly '\S{6}'
                'foobar' | Should -CMATCH '\S{6}'
            }
        }
    }
    
  • tools\Functions\Assertions\PesterThrow.ps1 Show
    function PesterThrow([scriptblock] $ActualValue, $ExpectedMessage, $ErrorId, [switch] $Negate) {
        $script:ActualExceptionMessage = ""
        $script:ActualExceptionWasThrown = $false
    
        if ($null -eq $ActualValue) {
            throw (New-Object -TypeName ArgumentNullException -ArgumentList "ActualValue","Scriptblock not found. Input to 'Throw' and 'Not Throw' must be enclosed in curly braces.")
        }
    
        # This is superfluous, here for now.
        $ExpectedErrorId = $ErrorId
    
        try {
            do {
                $null = & $ActualValue
            } until ($true)
        } catch {
            $script:ActualExceptionWasThrown = $true
            $script:ActualExceptionMessage = $_.Exception.Message
            $script:ActualErrorId = $_.FullyQualifiedErrorId
            $script:ActualExceptionLine = Get-ExceptionLineInfo $_.InvocationInfo
        }
    
        [bool] $succeeded = $false
    
        if ($ActualExceptionWasThrown) {
            $succeeded = (Get-DoValuesMatch $script:ActualExceptionMessage $ExpectedMessage) -and
                         (Get-DoValuesMatch $script:ActualErrorId $ExpectedErrorId)
        }
    
        if ($Negate) { $succeeded = -not $succeeded }
    
        $failureMessage = ''
    
        if (-not $succeeded)
        {
            if ($Negate)
            {
                $failureMessage = NotPesterThrowFailureMessage -ActualValue $ActualValue -ExpectedMessage $ExpectedMessage -ExpectedErrorId $ExpectedErrorId
            }
            else
            {
                $failureMessage = PesterThrowFailureMessage -ActualValue $ActualValue -ExpectedMessage $ExpectedMessage -ExpectedErrorId $ExpectedErrorId
            }
        }
    
        return New-Object psobject -Property @{
            Succeeded      = $succeeded
            FailureMessage = $failureMessage
        }
    }
    
    function Get-DoValuesMatch($ActualValue, $ExpectedValue) {
        #user did not specify any message filter, so any message matches
        if ($null -eq $ExpectedValue ) { return $true }
    
        return $ActualValue.ToString().IndexOf($ExpectedValue, [System.StringComparison]::InvariantCultureIgnoreCase) -ge 0
    }
    
    function Get-ExceptionLineInfo($info) {
        # $info.PositionMessage has a leading blank line that we need to account for in PowerShell 2.0
        $positionMessage = $info.PositionMessage -split '\r?\n' -match '\S' -join "`r`n"
        return ($positionMessage -replace "^At ","from ")
    }
    
    function PesterThrowFailureMessage($ActualValue, $ExpectedMessage, $ExpectedErrorId) {
        $StringBuilder = Microsoft.PowerShell.Utility\New-Object System.Text.StringBuilder
        $null = $StringBuilder.Append('Expected: the expression to throw an exception')
    
        if ($ExpectedMessage -or $ExpectedErrorId)
        {
            $null = $StringBuilder.Append(' with ')
            $Expected = switch ($null)
            {
                { $ExpectedMessage } { 'message {{{0}}}' -f $ExpectedMessage }
                { $ExpectedErrorId } { 'error id {{{0}}}' -f $ExpectedErrorId }
            }
            $Actual = switch ($null)
            {
                { $ExpectedMessage } { 'message was {{{0}}}' -f $ActualExceptionMessage }
                { $ExpectedErrorId } { 'error id was {{{0}}}' -f $ActualErrorId }
            }
            $null = $StringBuilder.Append(("{0}, an exception was {1}raised, {2}`n    {3}" -f
                ($Expected -join ' and '),
                @{$true="";$false="not "}[$ActualExceptionWasThrown],
                ($Actual -join ' and '),
                ($ActualExceptionLine  -replace "`n","`n    ")
            ))
        }
    
        return $StringBuilder.ToString()
    }
    
    function NotPesterThrowFailureMessage($ActualValue, $ExpectedMessage, $ExpectedErrorId) {
        $StringBuilder = New-Object System.Text.StringBuilder
        $null = $StringBuilder.Append('Expected: the expression not to throw an exception')
    
        if ($ExpectedMessage -or $ExpectedErrorId)
        {
            $null = $StringBuilder.Append(' with ')
            $Expected = switch ($null)
            {
                { $ExpectedMessage } { 'message {{{0}}}' -f $ExpectedMessage }
                { $ExpectedErrorId } { 'error id {{{0}}}' -f $ExpectedErrorId }
            }
            $Actual = switch ($null)
            {
                { $ExpectedMessage } { 'message was {{{0}}}' -f $ActualExceptionMessage }
                { $ExpectedErrorId } { 'error id was {{{0}}}' -f $ActualErrorId }
            }
            $null = $StringBuilder.Append(("{0}, an exception was {1}raised, {2}`n    {3}" -f
                ($Expected -join ' and '),
                (@{$true="";$false="not "}[$ActualExceptionWasThrown]),
                ($Actual -join ' and '),
                ($ActualExceptionLine  -replace "`n","`n    ")
            ))
        }
        else
        {
          $null = $StringBuilder.Append((". Message was {{{0}}}`n    {1}" -f $ActualExceptionMessage, ($ActualExceptionLine -replace "`n","`n    ")))
        }
    
        return $StringBuilder.ToString()
    }
    
    Add-AssertionOperator -Name Throw `
                          -Test $function:PesterThrow
    
  • tools\Functions\Assertions\PesterThrow.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "PesterThrow" {
            It "returns true if the statement throws an exception" {
                { throw } | Should Throw
                { throw } | Should -Throw
            }
    
            It "returns false if the statement does not throw an exception" {
                { 1 + 1 } | Should Not Throw
                { 1 + 1 } | Should -Not -Throw
            }
    
            It "returns true if the statement throws an exception and the actual error text matches the expected error text" {
                $expectedErrorMessage = "expected error message"
                { throw $expectedErrorMessage } | Should Throw $expectedErrorMessage
                { throw $expectedErrorMessage } | Should -Throw $expectedErrorMessage
            }
    
            It "returns true if the statement throws an exception and the actual error text matches the expected error text (case insensitive)" {
                $expectedErrorMessage = "expected error message"
                $errorMessage = $expectedErrorMessage.ToUpperInvariant()
                { throw $errorMessage } | Should Throw $expectedErrorMessage
                { throw $errorMessage } | Should -Throw $expectedErrorMessage
            }
    
            It "returns false if the statement throws an exception and the actual error does not match the expected error text" {
                $unexpectedErrorMessage = "unexpected error message"
                $expectedErrorMessage = "some expected error message"
                { throw $unexpectedErrorMessage } | Should Not Throw $expectedErrorMessage
                { throw $unexpectedErrorMessage } | Should -Not -Throw $expectedErrorMessage
            }
    
            It "returns true if the statement throws an exception and the actual error text matches the expected error pattern" {
                { throw 'expected error' } | Should Throw 'error'
                { throw 'expected error' } | Should -Throw 'error'
            }
    
            It "returns true if the statement throws an exception and the actual fully-qualified error id matches the expected error id" {
                $expectedErrorId = "expected error id"
                $ScriptBlock = {
                    $errorRecord = New-Object System.Management.Automation.ErrorRecord(
                        (New-Object Exception),
                        $expectedErrorId,
                        'OperationStopped',
                        $null
                    )
                    throw $errorRecord
                }
    
                # This syntax only. Not supported by Legacy.
                $ScriptBlock | Should -Throw -ErrorId $expectedErrorId
            }
    
            It "returns false if the statement throws an exception and the actual fully-qualified error id does not match the expected error id" {
                $unexpectedErrorId = "unexpected error id"
                $expectedErrorId = "some expected error id"
                # Likely a known artefact. There's an edge case that breaks the Contains-based comparison.
                # $unexpectedErrorId = "unexpected error id"
                # $expectedErrorId = "expected error id"
    
                $ScriptBlock = {
                    $errorRecord = New-Object System.Management.Automation.ErrorRecord(
                        (New-Object Exception),
                        $unexpectedErrorId,
                        'OperationStopped',
                        $null
                    )
                    throw $errorRecord
                }
    
                $ScriptBlock | Should -Not -Throw -ErrorId $expectedErrorId
            }
    
            It "returns true if the statement throws an exception and the actual error text and the fully-qualified error id match the expected error text and error id" {
                $expectedErrorMessage = "expected error message"
                $expectedErrorId = "some expected error id"
                $ScriptBlock = {
                    $errorRecord = New-Object System.Management.Automation.ErrorRecord(
                        (New-Object Exception $expectedErrorMessage),
                        $expectedErrorId,
                        'OperationStopped',
                        $null
                    )
                    throw $errorRecord
                }
    
                $ScriptBlock | Should -Throw $expectedErrorMessage -ErrorId $expectedErrorId
            }
    
            It "returns false if the statement throws an exception and the actual error text and fully-qualified error id do not match the expected error text and error id" {
                $unexpectedErrorMessage = "unexpected error message"
                $unexpectedErrorId = "unexpected error id"
                $expectedErrorMessage = "some expected error message"
                $expectedErrorId = "some expected error id"
                $ScriptBlock = {
                    $errorRecord = New-Object System.Management.Automation.ErrorRecord(
                        (New-Object Exception $unexpectedErrorMessage),
                        $unexpectedErrorId,
                        'OperationStopped',
                        $null
                    )
                    throw $errorRecord
                }
    
                $ScriptBlock | Should -Not -Throw $expectedErrorMessage -ErrorId $expectedErrorId
            }
    
            It "returns false if the statement throws an exception and the actual fully-qualified error id does not match the expected error id when the actual error text does match the expected error text" {
                $unexpectedErrorId = "unexpected error id"
                $expectedErrorMessage = "some expected error message"
                $expectedErrorId = "some expected error id"
                $ScriptBlock = {
                    $errorRecord = New-Object System.Management.Automation.ErrorRecord(
                        (New-Object Exception $expectedErrorMessage),
                        $unexpectedErrorId,
                        'OperationStopped',
                        $null
                    )
                    throw $errorRecord
                }
    
                $ScriptBlock | Should -Not -Throw $expectedErrorMessage -ErrorId $expectedErrorId
            }
    
            It "returns false if the statement throws an exception and the actual error text does not match the expected error text when the actual error id does match the expected error id" {
                $unexpectedErrorMessage = "unexpected error message"
                $expectedErrorMessage = "some expected error message"
                $expectedErrorId = "some expected error id"
                $ScriptBlock = {
                    $errorRecord = New-Object System.Management.Automation.ErrorRecord(
                        (New-Object Exception $unexpectedErrorMessage),
                        $expectedErrorId,
                        'OperationStopped',
                        $null
                    )
                    throw $errorRecord
                }
    
                $ScriptBlock | Should -Not -Throw $expectedErrorMessage -ErrorId $expectedErrorId
            }
    
            It "throws ArgumentException if null ScriptBlock is provided" {
                $e = $null
                try
                {
                    $null | Should Throw
                }
                catch
                {
                    $e = $_
                }
    
                $e | Should Not Be $null
                $e.Exception | Should BeOfType ArgumentException
            }
        }
    
        Describe "Get-DoMessagesMatch" {
            It "returns true if the actual message is the same as the expected message" {
                $expectedErrorMessage = "expected"
                $actualErrorMessage = "expected"
                $result = Get-DoValuesMatch $actualErrorMessage $expectedErrorMessage
                $result | Should Be $True
                $result | Should -Be $True
            }
    
            It "returns false if the actual message is not the same as the expected message" {
                $expectedErrorMessage = "some expected message"
                $actualErrorMessage = "unexpected"
                $result = Get-DoValuesMatch $actualErrorMessage $expectedErrorMessage
                $result | Should Be $False
                $result | Should -Be $False
            }
    
            It "returns true is there's no expectation" {
                $result = Get-DoValuesMatch "any error message" #expectation is null
                $result | Should Be $True
                $result | Should -Be $True
            }
    
            It "returns true if the message is empty and the expectation is empty" {
                $result = Get-DoValuesMatch "" ""
                $result | Should Be $True
                $result | Should -Be $True
            }
    
            It "returns true if the message is empty and there is no expectation" {
                $result = Get-DoValuesMatch "" #expectation is null
                $result | Should Be $True
                $result | Should -Be $True
            }
    
            It "returns true if the expected error is contained in the actual message" {
                $actualErrorMessage = "this is a long error message"
                $expectedText = "long error"
                $result = Get-DoValuesMatch $actualErrorMessage $expectedText
                $result | Should Be $True
                $result | Should -Be $True
            }
        }
    
        Describe 'PesterThrowFailureMessage' {
            $testScriptPath = Join-Path $TestDrive.FullName test.ps1
    
            It 'returns false if the actual message is not the same as the expected message' {
                $unexpectedErrorMessage = 'unexpected'
                $expectedErrorMessage = 'some expected message'
                Set-Content -Path $testScriptPath -Value "throw '$unexpectedErrorMessage'"
    
                PesterThrow { & $testScriptPath } $expectedErrorMessage > $null
                $result = PesterThrowFailureMessage $unexpectedErrorMessage $expectedErrorMessage
                $result | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
                $result | Should -Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
            }
    
            It 'returns true if the actual message is the same as the expected message' {
                PesterThrow { } > $null
                $result = PesterThrowFailureMessage 'error message'
                $result | Should Be 'Expected: the expression to throw an exception'
                $result | Should -Be 'Expected: the expression to throw an exception'
            }
    
            It 'returns false if the actual error id is not the same as the expected error id' {
                $unexpectedErrorId = 'unexpected error id'
                $expectedErrorId = 'some expected error id'
                Set-Content -Path $testScriptPath -Value "
                    `$errorRecord = New-Object System.Management.Automation.ErrorRecord(
                        (New-Object Exception),
                        '$unexpectedErrorId',
                        'OperationStopped',
                        `$null
                    )
                    throw `$errorRecord
                "
    
                PesterThrow { & $testScriptPath } -ErrorId $expectedErrorId > $null
                $result = PesterThrowFailureMessage $null -ExpectedErrorId $expectedErrorId
                $result | Should Match "^Expected: the expression to throw an exception with error id {$expectedErrorId}, an exception was raised, error id was {$unexpectedErrorId}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
                $result | Should -Match "^Expected: the expression to throw an exception with error id {$expectedErrorId}, an exception was raised, error id was {$unexpectedErrorId}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
            }
    
            It 'returns true if the actual error id is the same as the expected error id' {
                $expectedErrorId = 'some expected error id'
                Set-Content -Path $testScriptPath -Value "
                    `$errorRecord = New-Object System.Management.Automation.ErrorRecord(
                        (New-Object Exception),
                        '$expectedErrorId',
                        'OperationStopped',
                        `$null
                    )
                    throw `$errorRecord
                "
    
                PesterThrow { & $testScriptPath } -ErrorId $expectedErrorId > $null
                $result = PesterThrowFailureMessage $null -ErrorId $expectedErrorId
                $result | Should Match "^Expected: the expression to throw an exception"
                $result | Should -Match "^Expected: the expression to throw an exception"
            }
    
            It 'returns false if the actual message and error id are not the same as the expected message and error id' {
                $unexpectedErrorMessage = 'unexpected'
                $unexpectedErrorId = 'unexpected error id'
                $expectedErrorMessage = 'some expected message'
                $expectedErrorId = 'some expected error id'
                Set-Content -Path $testScriptPath -Value "
                    `$errorRecord = New-Object System.Management.Automation.ErrorRecord(
                        (New-Object Exception '$unexpectedErrorMessage'),
                        '$unexpectedErrorId',
                        'OperationStopped',
                        `$null
                    )
                    throw `$errorRecord
                "
    
                PesterThrow { & $testScriptPath } $expectedErrorMessage > $null
                $result = PesterThrowFailureMessage $null $expectedErrorMessage $expectedErrorId
                $result | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$unexpectedErrorMessage} and error id was {$unexpectedErrorId}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
                $result | Should -Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$unexpectedErrorMessage} and error id was {$unexpectedErrorId}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
            }
    
            It 'returns false if the actual message is not the same as the expected message when the actual error id and expected error id match' {
                $unexpectedErrorMessage = 'unexpected'
                $expectedErrorMessage = 'some expected message'
                $expectedErrorId = 'some expected error id'
                Set-Content -Path $testScriptPath -Value "
                    `$errorRecord = New-Object System.Management.Automation.ErrorRecord(
                        (New-Object Exception '$unexpectedErrorMessage'),
                        '$expectedErrorId',
                        'OperationStopped',
                        `$null
                    )
                    throw `$errorRecord
                "
    
                PesterThrow { & $testScriptPath } $expectedErrorMessage > $null
                $result = PesterThrowFailureMessage $null $expectedErrorMessage $expectedErrorId
                $result | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$unexpectedErrorMessage} and error id was {$expectedErrorId}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
                $result | Should -Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$unexpectedErrorMessage} and error id was {$expectedErrorId}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
            }
    
            It 'returns false if the actual error id is not the same as the expected error id when the actual message and expected message match' {
                $unexpectedErrorId = 'unexpected error id'
                $expectedErrorMessage = 'some expected message'
                $expectedErrorId = 'some expected error id'
                Set-Content -Path $testScriptPath -Value "
                    `$errorRecord = New-Object System.Management.Automation.ErrorRecord(
                        (New-Object Exception '$expectedErrorMessage'),
                        '$unexpectedErrorId',
                        'OperationStopped',
                        `$null
                    )
                    throw `$errorRecord
                "
    
                PesterThrow { & $testScriptPath } $expectedErrorMessage > $null
                $result = PesterThrowFailureMessage $null $expectedErrorMessage $expectedErrorId
                $result | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$expectedErrorMessage} and error id was {$unexpectedErrorId}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
                $result | Should -Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$expectedErrorMessage} and error id was {$unexpectedErrorId}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
            }
        }
    
        Describe 'NotPesterThrowFailureMessage' {
            $testScriptPath = Join-Path $TestDrive.FullName test.ps1
    
            # Shouldn't this test be using -Negate?
            It 'returns false if the actual message is not the same as the expected message' {
                $unexpectedErrorMessage = 'unexpected'
                $expectedErrorMessage = 'some expected message'
                Set-Content -Path $testScriptPath -Value "throw '$unexpectedErrorMessage'"
    
                $result = PesterThrow { & $testScriptPath } $expectedErrorMessage
                $result.FailureMessage | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
                $result.FailureMessage | Should -Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
            }
    
            It 'returns true if the actual message is the same as the expected message' {
                Set-Content -Path $testScriptPath -Value "throw 'error message'"
                $result = PesterThrow { & $testScriptPath } -Negate
                $result.FailureMessage | Should Match "^Expected: the expression not to throw an exception. Message was {error message}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
                $result.FailureMessage | Should -Match "^Expected: the expression not to throw an exception. Message was {error message}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
            }
    
            It 'returns false if the actual error id is the same as the expected error id' {
                $expectedErrorId = 'some expected error id'
                Set-Content -Path $testScriptPath -Value "
                    `$errorRecord = New-Object System.Management.Automation.ErrorRecord(
                        (New-Object Exception),
                        '$expectedErrorId',
                        'OperationStopped',
                        `$null
                    )
                    throw `$errorRecord
                "
    
                $result = PesterThrow { & $testScriptPath } -ErrorId $expectedErrorId -Negate
                $result.FailureMessage | Should Match "^Expected: the expression not to throw an exception with error id {$expectedErrorId}, an exception was raised, error id was {$expectedErrorId}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
                $result.FailureMessage | Should -Match "^Expected: the expression not to throw an exception with error id {$expectedErrorId}, an exception was raised, error id was {$expectedErrorId}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
            }
    
            It 'returns false if the actual message or actual error id is the same as the expected message or expected error id' {
                $expectedErrorMessage = 'some expected message'
                $expectedErrorId = 'some expected error id'
                Set-Content -Path $testScriptPath -Value "
                    `$errorRecord = New-Object System.Management.Automation.ErrorRecord(
                        (New-Object Exception '$expectedErrorMessage'),
                        '$expectedErrorId',
                        'OperationStopped',
                        `$null
                    )
                    throw `$errorRecord
                "
    
                $result = PesterThrow { & $testScriptPath } $expectedErrorMessage -ErrorId $expectedErrorId -Negate
                $result.FailureMessage | Should Match "^Expected: the expression not to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$expectedErrorMessage} and error id was {$expectedErrorId}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
                $result.FailureMessage | Should -Match "^Expected: the expression not to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$expectedErrorMessage} and error id was {$expectedErrorId}`n    from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+"
            }
        }
    }
    
  • tools\Functions\Assertions\Set-TestInconclusive.ps1 Show
    function New-InconclusiveErrorRecord ([string] $Message, [string] $File, [string] $Line, [string] $LineText) {
        $exception = New-Object Exception $Message
        $errorID = 'PesterTestInconclusive'
        $errorCategory = [Management.Automation.ErrorCategory]::InvalidResult
        # we use ErrorRecord.TargetObject to pass structured information about the error to a reporting system.
        $targetObject = @{Message = $Message; File = $File; Line = $Line; LineText = $LineText}
        $errorRecord = New-Object Management.Automation.ErrorRecord $exception, $errorID, $errorCategory, $targetObject
        return $errorRecord
    }
    
    function Set-TestInconclusive {
        param (
            [string] $Message
        )
    
        Assert-DescribeInProgress -CommandName Set-TestInconclusive
        $lineText = $MyInvocation.Line.TrimEnd("`n")
        $line = $MyInvocation.ScriptLineNumber
        $file = $MyInvocation.ScriptName
    
        throw ( New-InconclusiveErrorRecord -Message $Message -File $file -Line $line -LineText $lineText)
    }
    
  • tools\Functions\Assertions\Should.ps1 Show
    function Parse-ShouldArgs([object[]] $shouldArgs) {
        if ($null -eq $shouldArgs) { $shouldArgs = @() }
    
        $parsedArgs = @{
            PositiveAssertion = $true
            ExpectedValue = $null
        }
    
        $assertionMethodIndex = 0
        $expectedValueIndex   = 1
    
        if ($shouldArgs.Count -gt 0 -and $shouldArgs[0] -eq "not") {
            $parsedArgs.PositiveAssertion = $false
            $assertionMethodIndex += 1
            $expectedValueIndex   += 1
        }
    
        if ($assertionMethodIndex -lt $shouldArgs.Count)
        {
            $parsedArgs.AssertionMethod = "$($shouldArgs[$assertionMethodIndex])"
        }
        else
        {
            throw 'You cannot call Should without specifying an assertion method.'
        }
    
        if ($expectedValueIndex -lt $shouldArgs.Count)
        {
            $parsedArgs.ExpectedValue = $shouldArgs[$expectedValueIndex]
        }
    
        return $parsedArgs
    }
    
    function Get-FailureMessage($assertionEntry, $negate, $value, $expected) {
        if ($negate)
        {
            $failureMessageFunction = $assertionEntry.GetNegativeFailureMessage
        }
        else
        {
            $failureMessageFunction = $assertionEntry.GetPositiveFailureMessage
        }
    
        return (& $failureMessageFunction $value $expected)
    }
    
    function New-ShouldErrorRecord ([string] $Message, [string] $File, [string] $Line, [string] $LineText) {
        $exception = & $SafeCommands['New-Object'] Exception $Message
        $errorID = 'PesterAssertionFailed'
        $errorCategory = [Management.Automation.ErrorCategory]::InvalidResult
        # we use ErrorRecord.TargetObject to pass structured information about the error to a reporting system.
        $targetObject = @{Message = $Message; File = $File; Line = $Line; LineText = $LineText}
        $errorRecord = & $SafeCommands['New-Object'] Management.Automation.ErrorRecord $exception, $errorID, $errorCategory, $targetObject
        return $errorRecord
    }
    
    function Should
    {
        [CmdletBinding(DefaultParameterSetName = 'Legacy')]
        param (
            [Parameter(ParameterSetName = 'Legacy', Position = 0)]
            [object] $LegacyArg1,
    
            [Parameter(ParameterSetName = 'Legacy', Position = 1)]
            [object] $LegacyArg2,
    
            [Parameter(ParameterSetName = 'Legacy', Position = 2)]
            [object] $LegacyArg3,
    
            [Parameter(ValueFromPipeline = $true)]
            [object] $ActualValue
        )
    
        dynamicparam
        {
            Get-AssertionDynamicParams
        }
    
        begin
        {
            #Assert-DescribeInProgress -CommandName Should
    
            $inputArray = New-Object System.Collections.ArrayList
    
            if ($PSCmdlet.ParameterSetName -eq 'Legacy')
            {
                $parsedArgs = Parse-ShouldArgs ($LegacyArg1, $LegacyArg2, $LegacyArg3)
                $entry = Get-AssertionOperatorEntry -Name $parsedArgs.AssertionMethod
                if ($null -eq $entry)
                {
                    throw "'$($parsedArgs.AssertionMethod)' is not a valid Should operator."
                }
            }
        }
    
        process
        {
            $null = $inputArray.Add($ActualValue)
        }
    
        end
        {
            $lineNumber = $MyInvocation.ScriptLineNumber
            $lineText   = $MyInvocation.Line.TrimEnd("`n")
            $file       = $MyInvocation.ScriptName
    
            if ($PSCmdlet.ParameterSetName -eq 'Legacy')
            {
                if ($inputArray.Count -eq 0)
                {
                    Invoke-LegacyAssertion $entry $parsedArgs $null $file $lineNumber $lineText
                }
                elseif ($entry.SupportsArrayInput)
                {
                    Invoke-LegacyAssertion $entry $parsedArgs $inputArray.ToArray() $file $lineNumber $lineText
                }
                else
                {
                    foreach ($object in $inputArray)
                    {
                        Invoke-LegacyAssertion $entry $parsedArgs $object $file $lineNumber $lineText
                    }
                }
            }
            else
            {
                $negate = $false
                if ($PSBoundParameters.ContainsKey('Not'))
                {
                    $negate = [bool]$PSBoundParameters['Not']
                }
    
                $null = $PSBoundParameters.Remove('ActualValue')
                $null = $PSBoundParameters.Remove($PSCmdlet.ParameterSetName)
                $null = $PSBoundParameters.Remove('Not')
    
                $entry = Get-AssertionOperatorEntry -Name $PSCmdlet.ParameterSetName
    
                if ($inputArray.Count -eq 0)
                {
                    Invoke-Assertion $entry $PSBoundParameters $null $file $lineNumber $lineText -Negate:$negate
                }
                elseif ($entry.SupportsArrayInput)
                {
                    Invoke-Assertion $entry $PSBoundParameters $inputArray.ToArray() $file $lineNumber $lineText -Negate:$negate
                }
                else
                {
                    foreach ($object in $inputArray)
                    {
                        Invoke-Assertion $entry $PSBoundParameters $object $file $lineNumber $lineText -Negate:$negate
                    }
                }
            }
        }
    }
    
    function Invoke-LegacyAssertion($assertionEntry, $shouldArgs, $valueToTest, $file, $lineNumber, $lineText)
    {
        # $expectedValueSplat = @(
        #     if ($null -ne $shouldArgs.ExpectedValue)
        #     {
        #         ,$shouldArgs.ExpectedValue
        #     }
        # )
    
        $negate = -not $shouldArgs.PositiveAssertion
    
        $testResult = (& $assertionEntry.Test $valueToTest $shouldArgs.ExpectedValue -Negate:$negate)
        if (-not $testResult.Succeeded)
        {
            throw ( New-ShouldErrorRecord -Message $testResult.FailureMessage -File $file -Line $lineNumber -LineText $lineText )
        }
    }
    
    function Invoke-Assertion
    {
        param (
            [object] $AssertionEntry,
            [System.Collections.IDictionary] $BoundParameters,
            [object] $valuetoTest,
            [string] $File,
            [int] $LineNumber,
            [string] $LineText,
            [switch] $Negate
        )
    
        $testResult = & $AssertionEntry.Test -ActualValue $valuetoTest -Negate:$Negate @BoundParameters
        if (-not $testResult.Succeeded)
        {
            throw ( New-ShouldErrorRecord -Message $testResult.FailureMessage -File $file -Line $lineNumber -LineText $lineText )
        }
    }
    
  • tools\Functions\Assertions\Should.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "Parse-ShouldArgs" {
            It "identifies assertion functions" {
                $parsedArgs = Parse-ShouldArgs TestFunction
                $parsedArgs.AssertionMethod | Should Be TestFunction
    
                $parsedArgs = Parse-ShouldArgs Not, TestFunction
                $parsedArgs.AssertionMethod | Should Be TestFunction
            }
    
            It "works with strict mode when using 'switch' style tests" {
                Set-StrictMode -Version Latest
                { throw 'Test' } | Should Throw
                { throw 'Test' } | Should -Throw
            }
    
            Context "for positive assertions" {
    
                $parsedArgs = Parse-ShouldArgs testMethod, 1
    
                It "gets the expected value from the 2nd argument" {
                    $ParsedArgs.ExpectedValue | Should Be 1
                }
    
                It "marks the args as a positive assertion" {
                    $ParsedArgs.PositiveAssertion | Should Be $true
                }
            }
    
            Context "for negative assertions" {
    
                $parsedArgs = Parse-ShouldArgs Not, testMethod, 1
    
                It "gets the expected value from the third argument" {
                    $ParsedArgs.ExpectedValue | Should Be 1
                }
    
                It "marks the args as a negative assertion" {
                    $ParsedArgs.PositiveAssertion | Should Be $false
                }
            }
        }
    
        Describe -Tag "Acceptance" "Should" {
            It "can use the Be assertion" {
                1 | Should Be 1
                1 | Should -Be 1
            }
    
            It "can use the Not Be assertion" {
                1 | Should Not Be 2
                1 | Should -Not -Be 2
            }
    
            It "can use the BeNullOrEmpty assertion" {
                $null | Should BeNullOrEmpty
                @()   | Should BeNullOrEmpty
                ""    | Should BeNullOrEmpty
                $null | Should -BeNullOrEmpty
                @()   | Should -BeNullOrEmpty
                ""    | Should -BeNullOrEmpty
            }
    
            It "can use the Not BeNullOrEmpty assertion" {
                @("foo") | Should Not BeNullOrEmpty
                "foo"    | Should Not BeNullOrEmpty
                "   "    | Should Not BeNullOrEmpty
                @(1,2,3) | Should Not BeNullOrEmpty
                12345    | Should Not BeNullOrEmpty
                @("foo") | Should -Not -BeNullOrEmpty
                "foo"    | Should -Not -BeNullOrEmpty
                "   "    | Should -Not -BeNullOrEmpty
                @(1,2,3) | Should -Not -BeNullOrEmpty
                12345    | Should -Not -BeNullOrEmpty
                $item1 = New-Object PSObject -Property @{Id=1; Name="foo"}
                $item2 = New-Object PSObject -Property @{Id=2; Name="bar"}
                @($item1, $item2) | Should Not BeNullOrEmpty
                @($item1, $item2) | Should -Not -BeNullOrEmpty
            }
    
            It "can handle exception thrown assertions" {
                { foo } | Should Throw
                { foo } | Should -Throw
            }
    
            It "can handle exception should not be thrown assertions" {
                { $foo = 1 } | Should Not Throw
                { $foo = 1 } | Should -Not -Throw
            }
    
            It "can handle Exist assertion" {
                $TestDrive | Should Exist
                $TestDrive | Should -Exist
            }
    
            It "can handle the Match assertion" {
                "abcd1234" | Should Match "d1"
                "abcd1234" | Should -Match "d1"
            }
    
            It "can test for file contents" {
                Setup -File "test.foo" "expected text"
                "$TestDrive\test.foo" | Should Contain "expected text"
                "$TestDrive\test.foo" | Should -Contain "expected text"
            }
    
            It "ensures all assertion functions provide failure messages" {
                $assertionFunctions = @("PesterBe", "PesterThrow", "PesterBeNullOrEmpty", "PesterExist",
                    "PesterMatch", "PesterContain")
                $assertionFunctions | % {
                    "function:$($_)FailureMessage" | Should Exist
                    "function:Not$($_)FailureMessage" | Should Exist
                    "function:$($_)FailureMessage" | Should -Exist
                    "function:Not$($_)FailureMessage" | Should -Exist
                }
            }
    
            # TODO understand the purpose of this test, perhaps some better wording
            It "can process functions with empty output as input" {
                function ReturnNothing {}
    
                # TODO figure out why this is the case
                if ($PSVersionTable.PSVersion -eq "2.0") {
                    { $(ReturnNothing) | Should Not BeNullOrEmpty } | Should Not Throw
                    { $(ReturnNothing) | Should -Not -BeNullOrEmpty } | Should -Not -Throw
                } else {
                    { $(ReturnNothing) | Should Not BeNullOrEmpty } | Should Throw
                    { $(ReturnNothing) | Should -Not -BeNullOrEmpty } | Should -Throw
                }
            }
    
            # Assertion messages aren't convention-based anymore, but we should probably still make sure
            # that our tests are complete (including negative tests) to verify the proper messages get
            # returned.  Those will need to exist in other tests files.
    
            <#
            It 'All failure message functions are present' {
                $assertionFunctions = Get-Command -CommandType Function -Module Pester |
                                      Select-Object -ExpandProperty Name |
                                      Where-Object { $_ -like 'Pester*' -and $_ -notlike '*FailureMessage' }
    
                $missingFunctions = @(
                    foreach ($assertionFunction in $assertionFunctions)
                    {
                        $positiveFailureMessage = "${assertionFunction}FailureMessage"
                        $negativeFailureMessage = "Not${assertionFunction}FailureMessage"
    
                        if (-not (Test-Path function:$positiveFailureMessage)) { $positiveFailureMessage }
                        if (-not (Test-Path function:$negativeFailureMessage)) { $negativeFailureMessage }
                    }
                )
    
                [string]$missingFunctions | Should BeNullOrEmpty
            }
            #>
        }
    }
    
  • tools\Functions\BreakAndContinue.Tests.ps1 Show
    Describe 'Clean handling of break and continue' {
        # If this test 'fails', it'll just cause most of the rest of the tests to never execute (and we won't see any actual failures.)
        # The CI job monitors metrics, though, and will fail the build if the number of tests drops too much.
    
        Context 'Break' {
            break
        }
    
        Context 'Continue' {
            continue
        }
    
        It 'Did not abort the whole test run' { $null = $null }
    }
    
  • tools\Functions\Context.ps1 Show
    function Context {
    <#
    .SYNOPSIS
    Provides logical grouping of It blocks within a single Describe block.
    
    .DESCRIPTION
    Provides logical grouping of It blocks within a single Describe block.
    Any Mocks defined inside a Context are removed at the end of the Context scope,
    as are any files or folders added to the TestDrive during the Context block's
    execution. Any BeforeEach or AfterEach blocks defined inside a Context also only
    apply to tests within that Context .
    
    .PARAMETER Name
    The name of the Context. This is a phrase describing a set of tests within a describe.
    
    .PARAMETER Fixture
    Script that is executed. This may include setup specific to the context
    and one or more It blocks that validate the expected outcomes.
    
    .EXAMPLE
    function Add-Numbers($a, $b) {
        return $a + $b
    }
    
    Describe "Add-Numbers" {
    
        Context "when root does not exist" {
             It "..." { ... }
        }
    
        Context "when root does exist" {
            It "..." { ... }
            It "..." { ... }
            It "..." { ... }
        }
    }
    
    .LINK
    Describe
    It
    BeforeEach
    AfterEach
    about_Should
    about_Mocking
    about_TestDrive
    
    #>
        param(
            [Parameter(Mandatory = $true, Position = 0)]
            [string] $Name,
    
            [Alias('Tags')]
            [email protected](),
    
            [Parameter(Position = 1)]
            [ValidateNotNull()]
            [ScriptBlock] $Fixture = $(Throw "No test script block is provided. (Have you put the open curly brace on the next line?)")
        )
    
        Describe @PSBoundParameters -CommandUsed Context
    }
    
  • tools\Functions\Coverage.ps1 Show
    if ($PSVersionTable.PSVersion.Major -le 2)
    {
        function Exit-CoverageAnalysis { }
        function Get-CoverageReport { }
        function Write-CoverageReport { }
        function Enter-CoverageAnalysis {
            param ( $CodeCoverage )
    
            if ($CodeCoverage) { & $SafeCommands['Write-Error'] 'Code coverage analysis requires PowerShell 3.0 or later.' }
        }
    
        return
    }
    
    function Enter-CoverageAnalysis
    {
        [CmdletBinding()]
        param (
            [object[]] $CodeCoverage,
            [object] $PesterState
        )
    
        $coverageInfo =
        foreach ($object in $CodeCoverage)
        {
            Get-CoverageInfoFromUserInput -InputObject $object
        }
    
        $PesterState.CommandCoverage = @(Get-CoverageBreakpoints -CoverageInfo $coverageInfo)
    }
    
    function Exit-CoverageAnalysis
    {
        param ([object] $PesterState)
    
        & $SafeCommands['Set-StrictMode'] -Off
    
        $breakpoints = @($PesterState.CommandCoverage.Breakpoint) -ne $null
        if ($breakpoints.Count -gt 0)
        {
            & $SafeCommands['Remove-PSBreakpoint'] -Breakpoint $breakpoints
        }
    }
    
    function Get-CoverageInfoFromUserInput
    {
        param (
            [Parameter(Mandatory = $true)]
            [object]
            $InputObject
        )
    
        if ($InputObject -is [System.Collections.IDictionary])
        {
            $unresolvedCoverageInfo = Get-CoverageInfoFromDictionary -Dictionary $InputObject
        }
        else
        {
            $unresolvedCoverageInfo = New-CoverageInfo -Path ([string]$InputObject)
        }
    
        Resolve-CoverageInfo -UnresolvedCoverageInfo $unresolvedCoverageInfo
    }
    
    function New-CoverageInfo
    {
        param ([string] $Path, [string] $Function = $null, [int] $StartLine = 0, [int] $EndLine = 0)
    
        return [pscustomobject]@{
            Path = $Path
            Function = $Function
            StartLine = $StartLine
            EndLine = $EndLine
        }
    }
    
    function Get-CoverageInfoFromDictionary
    {
        param ([System.Collections.IDictionary] $Dictionary)
    
        [string] $path = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'Path', 'p'
        if ([string]::IsNullOrEmpty($path))
        {
            throw "Coverage value '$Dictionary' is missing required Path key."
        }
    
        $startLine = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'StartLine', 'Start', 's'
        $endLine = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'EndLine', 'End', 'e'
        [string] $function = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'Function', 'f'
    
        $startLine = Convert-UnknownValueToInt -Value $startLine -DefaultValue 0
        $endLine = Convert-UnknownValueToInt -Value $endLine -DefaultValue 0
    
        return New-CoverageInfo -Path $path -StartLine $startLine -EndLine $endLine -Function $function
    }
    
    function Convert-UnknownValueToInt
    {
        param ([object] $Value, [int] $DefaultValue = 0)
    
        try
        {
            return [int] $Value
        }
        catch
        {
            return $DefaultValue
        }
    }
    
    function Resolve-CoverageInfo
    {
        param ([psobject] $UnresolvedCoverageInfo)
    
        $path = $UnresolvedCoverageInfo.Path
    
        try
        {
            $resolvedPaths = & $SafeCommands['Resolve-Path'] -Path $path -ErrorAction Stop
        }
        catch
        {
            & $SafeCommands['Write-Error'] "Could not resolve coverage path '$path': $($_.Exception.Message)"
            return
        }
    
        $filePaths =
        foreach ($resolvedPath in $resolvedPaths)
        {
            $item = & $SafeCommands['Get-Item'] -LiteralPath $resolvedPath
            if ($item -is [System.IO.FileInfo] -and ('.ps1','.psm1') -contains $item.Extension)
            {
                $item.FullName
            }
            elseif (-not $item.PsIsContainer)
            {
                & $SafeCommands['Write-Warning'] "CodeCoverage path '$path' resolved to a non-PowerShell file '$($item.FullName)'; this path will not be part of the coverage report."
            }
        }
    
        $params = @{
            StartLine = $UnresolvedCoverageInfo.StartLine
            EndLine = $UnresolvedCoverageInfo.EndLine
            Function = $UnresolvedCoverageInfo.Function
        }
    
        foreach ($filePath in $filePaths)
        {
            $params['Path'] = $filePath
            New-CoverageInfo @params
        }
    }
    
    function Get-CoverageBreakpoints
    {
        [CmdletBinding()]
        param (
            [object[]] $CoverageInfo
        )
    
        $fileGroups = @($CoverageInfo | & $SafeCommands['Group-Object'] -Property Path)
        foreach ($fileGroup in $fileGroups)
        {
            & $SafeCommands['Write-Verbose'] "Initializing code coverage analysis for file '$($fileGroup.Name)'"
            $totalCommands = 0
            $analyzedCommands = 0
    
            :commandLoop
            foreach ($command in Get-CommandsInFile -Path $fileGroup.Name)
            {
                $totalCommands++
    
                foreach ($coverageInfoObject in $fileGroup.Group)
                {
                    if (Test-CoverageOverlapsCommand -CoverageInfo $coverageInfoObject -Command $command)
                    {
                        $analyzedCommands++
                        New-CoverageBreakpoint -Command $command
                        continue commandLoop
                    }
                }
            }
    
            & $SafeCommands['Write-Verbose'] "Analyzing $analyzedCommands of $totalCommands commands in file '$($fileGroup.Name)' for code coverage"
        }
    }
    
    function Get-CommandsInFile
    {
        param ([string] $Path)
    
        $errors = $null
        $tokens = $null
        $ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref] $tokens, [ref] $errors)
    
        if ($PSVersionTable.PSVersion.Major -ge 5)
        {
            # In PowerShell 5.0, dynamic keywords for DSC configurations are represented by the DynamicKeywordStatementAst
            # class.  They still trigger breakpoints, but are not a child class of CommandBaseAst anymore.
    
            $predicate = {
                $args[0] -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -or
                $args[0] -is [System.Management.Automation.Language.CommandBaseAst]
            }
        }
        else
        {
            $predicate = { $args[0] -is [System.Management.Automation.Language.CommandBaseAst] }
        }
    
        $searchNestedScriptBlocks = $true
        $ast.FindAll($predicate, $searchNestedScriptBlocks)
    }
    
    function Test-CoverageOverlapsCommand
    {
        param ([object] $CoverageInfo, [System.Management.Automation.Language.Ast] $Command)
    
        if ($CoverageInfo.Function)
        {
            Test-CommandInsideFunction -Command $Command -Function $CoverageInfo.Function
        }
        else
        {
            Test-CoverageOverlapsCommandByLineNumber @PSBoundParameters
        }
    
    }
    
    function Test-CommandInsideFunction
    {
        param ([System.Management.Automation.Language.Ast] $Command, [string] $Function)
    
        for ($ast = $Command; $null -ne $ast; $ast = $ast.Parent)
        {
            $functionAst = $ast -as [System.Management.Automation.Language.FunctionDefinitionAst]
            if ($null -ne $functionAst -and $functionAst.Name -like $Function)
            {
                return $true
            }
        }
    
        return $false
    }
    
    function Test-CoverageOverlapsCommandByLineNumber
    {
        param ([object] $CoverageInfo, [System.Management.Automation.Language.Ast] $Command)
    
        $commandStart = $Command.Extent.StartLineNumber
        $commandEnd = $Command.Extent.EndLineNumber
        $coverStart = $CoverageInfo.StartLine
        $coverEnd = $CoverageInfo.EndLine
    
        # An EndLine value of 0 means to cover the entire rest of the file from StartLine
        # (which may also be 0)
        if ($coverEnd -le 0) { $coverEnd = [int]::MaxValue }
    
        return (Test-RangeContainsValue -Value $commandStart -Min $coverStart -Max $coverEnd) -or
               (Test-RangeContainsValue -Value $commandEnd -Min $coverStart -Max $coverEnd)
    }
    
    function Test-RangeContainsValue
    {
        param ([int] $Value, [int] $Min, [int] $Max)
        return $Value -ge $Min -and $Value -le $Max
    }
    
    function New-CoverageBreakpoint
    {
        param ([System.Management.Automation.Language.Ast] $Command)
    
        if (IsIgnoredCommand -Command $Command) { return }
    
        $params = @{
            Script = $Command.Extent.File
            Line   = $Command.Extent.StartLineNumber
            Column = $Command.Extent.StartColumnNumber
            Action = { }
        }
    
        $breakpoint = & $SafeCommands['Set-PSBreakpoint'] @params
    
        [pscustomobject] @{
            File       = $Command.Extent.File
            Function   = Get-ParentFunctionName -Ast $Command
            Line       = $Command.Extent.StartLineNumber
            Command    = Get-CoverageCommandText -Ast $Command
            Breakpoint = $breakpoint
        }
    }
    
    function IsIgnoredCommand
    {
        param ([System.Management.Automation.Language.Ast] $Command)
    
        if (-not $Command.Extent.File)
        {
            # This can happen if the script contains "configuration" or any similarly implemented
            # dynamic keyword.  PowerShell modifies the script code and reparses it in memory, leading
            # to AST elements with no File in their Extent.
            return $true
        }
    
        if ($PSVersionTable.PSVersion.Major -ge 4)
        {
            if ($Command.Extent.Text -eq 'Configuration')
            {
                # More DSC voodoo.  Calls to "configuration" generate breakpoints, but their HitCount
                # stays zero (even though they are executed.)  For now, ignore them, unless we can come
                # up with a better solution.
                return $true
            }
    
            if (IsChildOfHashtableDynamicKeyword -Command $Command)
            {
                # The lines inside DSC resource declarations don't trigger their breakpoints when executed,
                # just like the "configuration" keyword itself.  I don't know why, at this point, but just like
                # configuration, we'll ignore it so it doesn't clutter up the coverage analysis with useless junk.
                return $true
            }
        }
    
        if (IsClosingLoopCondition -Command $Command)
        {
            # For some reason, the closing expressions of do/while and do/until loops don't trigger their breakpoints.
            # To avoid useless clutter, we'll ignore those lines as well.
            return $true
        }
    
        return $false
    }
    
    function IsChildOfHashtableDynamicKeyword
    {
        param ([System.Management.Automation.Language.Ast] $Command)
    
        for ($ast = $Command.Parent; $null -ne $ast; $ast = $ast.Parent)
        {
            if ($PSVersionTable.PSVersion.Major -ge 5)
            {
                # The ast behaves differently for DSC resources with version 5+.  There's a new DynamicKeywordStatementAst class,
                # and they no longer are represented by CommandAst objects.
    
                if ($ast -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -and
                    $ast.CommandElements[-1] -is [System.Management.Automation.Language.HashtableAst])
                {
                    return $true
                }
            }
            else
            {
                if ($ast -is [System.Management.Automation.Language.CommandAst] -and
                    $null -ne $ast.DefiningKeyword -and
                    $ast.DefiningKeyword.BodyMode -eq [System.Management.Automation.Language.DynamicKeywordBodyMode]::Hashtable)
                {
                    return $true
                }
            }
        }
    
        return $false
    }
    
    function IsClosingLoopCondition
    {
        param ([System.Management.Automation.Language.Ast] $Command)
    
        $ast = $Command
    
        while ($null -ne $ast.Parent)
        {
            if (($ast.Parent -is [System.Management.Automation.Language.DoWhileStatementAst] -or
                $ast.Parent -is [System.Management.Automation.Language.DoUntilStatementAst]) -and
                $ast.Parent.Condition -eq $ast)
            {
                return $true
            }
    
            $ast = $ast.Parent
        }
    
        return $false
    }
    
    function Get-ParentFunctionName
    {
        param ([System.Management.Automation.Language.Ast] $Ast)
    
        $parent = $Ast.Parent
    
        while ($null -ne $parent -and $parent -isnot [System.Management.Automation.Language.FunctionDefinitionAst])
        {
            $parent = $parent.Parent
        }
    
        if ($null -eq $parent)
        {
            return ''
        }
        else
        {
            return $parent.Name
        }
    }
    
    function Get-CoverageCommandText
    {
        param ([System.Management.Automation.Language.Ast] $Ast)
    
        $reportParentExtentTypes = @(
            [System.Management.Automation.Language.ReturnStatementAst]
            [System.Management.Automation.Language.ThrowStatementAst]
            [System.Management.Automation.Language.AssignmentStatementAst]
            [System.Management.Automation.Language.IfStatementAst]
        )
    
        $parent = Get-ParentNonPipelineAst -Ast $Ast
    
        if ($null -ne $parent)
        {
            if ($parent -is [System.Management.Automation.Language.HashtableAst])
            {
                return Get-KeyValuePairText -HashtableAst $parent -ChildAst $Ast
            }
            elseif ($reportParentExtentTypes -contains $parent.GetType())
            {
                return $parent.Extent.Text
            }
        }
    
        return $Ast.Extent.Text
    }
    
    function Get-ParentNonPipelineAst
    {
        param ([System.Management.Automation.Language.Ast] $Ast)
    
        $parent = $null
        if ($null -ne $Ast) { $parent = $Ast.Parent }
    
        while ($parent -is [System.Management.Automation.Language.PipelineAst])
        {
            $parent = $parent.Parent
        }
    
        return $parent
    }
    
    function Get-KeyValuePairText
    {
        param (
            [System.Management.Automation.Language.HashtableAst] $HashtableAst,
            [System.Management.Automation.Language.Ast] $ChildAst
        )
    
        & $SafeCommands['Set-StrictMode'] -Off
    
        foreach ($keyValuePair in $HashtableAst.KeyValuePairs)
        {
            if ($keyValuePair.Item2.PipelineElements -contains $ChildAst)
            {
                return '{0} = {1}' -f $keyValuePair.Item1.Extent.Text, $keyValuePair.Item2.Extent.Text
            }
        }
    
        # This shouldn't happen, but just in case, default to the old output of just the expression.
        return $ChildAst.Extent.Text
    }
    
    function Get-CoverageMissedCommands
    {
        param ([object[]] $CommandCoverage)
        $CommandCoverage | & $SafeCommands['Where-Object'] { $_.Breakpoint.HitCount -eq 0 }
    }
    
    function Get-CoverageHitCommands
    {
        param ([object[]] $CommandCoverage)
        $CommandCoverage | & $SafeCommands['Where-Object'] { $_.Breakpoint.HitCount -gt 0 }
    }
    
    function Get-CoverageReport
    {
        param ([object] $PesterState)
    
        $totalCommandCount = $PesterState.CommandCoverage.Count
    
        $missedCommands = @(Get-CoverageMissedCommands -CommandCoverage $PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command)
        $hitCommands = @(Get-CoverageHitCommands -CommandCoverage $PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command)
        $analyzedFiles = @($PesterState.CommandCoverage | & $SafeCommands['Select-Object'] -ExpandProperty File -Unique)
        $fileCount = $analyzedFiles.Count
    
        $executedCommandCount = $totalCommandCount - $missedCommands.Count
    
        [pscustomobject] @{
            NumberOfCommandsAnalyzed = $totalCommandCount
            NumberOfFilesAnalyzed    = $fileCount
            NumberOfCommandsExecuted = $executedCommandCount
            NumberOfCommandsMissed   = $missedCommands.Count
            MissedCommands           = $missedCommands
            HitCommands              = $hitCommands
            AnalyzedFiles            = $analyzedFiles
        }
    }
    
    function Get-CommonParentPath
    {
        param ([string[]] $Path)
    
        $pathsToTest = @(
            $Path |
            Normalize-Path |
            & $SafeCommands['Select-Object'] -Unique
        )
    
        if ($pathsToTest.Count -gt 0)
        {
            $parentPath = & $SafeCommands['Split-Path'] -Path $pathsToTest[0] -Parent
    
            while ($parentPath.Length -gt 0)
            {
                $nonMatches = $pathsToTest -notmatch "^$([regex]::Escape($parentPath))"
    
                if ($nonMatches.Count -eq 0)
                {
                    return $parentPath
                }
                else
                {
                    $parentPath = & $SafeCommands['Split-Path'] -Path $parentPath -Parent
                }
            }
        }
    
        return [string]::Empty
    }
    
    function Get-RelativePath
    {
        param ( [string] $Path, [string] $RelativeTo )
        return $Path -replace "^$([regex]::Escape("$RelativeTo$([System.IO.Path]::DirectorySeparatorChar)"))?"
    }
    
    function Normalize-Path
    {
        [CmdletBinding()]
        param (
            [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
            [Alias('PSPath', 'FullName')]
            [string[]] $Path
        )
    
        # Split-Path and Join-Path will replace any AltDirectorySeparatorChar instances with the DirectorySeparatorChar
        # (Even if it's not the one that the split / join happens on.)  So splitting / rejoining a path will give us
        # consistent separators for later string comparison.
    
        process
        {
            if ($null -ne $Path)
            {
                foreach ($p in $Path)
                {
                    $normalizedPath = & $SafeCommands['Split-Path'] $p -Leaf
    
                    if ($normalizedPath -ne $p)
                    {
                        $parent = & $SafeCommands['Split-Path'] $p -Parent
                        $normalizedPath = & $SafeCommands['Join-Path'] $parent $normalizedPath
                    }
    
                    $normalizedPath
                }
            }
        }
    }
    
  • tools\Functions\Coverage.Tests.ps1 Show
    if ($PSVersionTable.PSVersion.Major -le 2) { return }
    
    InModuleScope Pester {
        Describe 'Code Coverage Analysis' {
            $root = (Get-PSDrive TestDrive).Root
    
            $null = New-Item -Path $root\TestScript.ps1 -ItemType File -ErrorAction SilentlyContinue
    
            Set-Content -Path $root\TestScript.ps1 -Value @'
                function FunctionOne
                {
                    function NestedFunction
                    {
                        'I am the nested function.'
                        'I get fully executed.'
                    }
    
                    if ($true)
                    {
                        'I am functionOne'
                        NestedFunction
                    }
                }
    
                function FunctionTwo
                {
                    'I am function two.  I never get called.'
                }
    
                FunctionOne
    
    '@
    
            Context 'Entire file' {
                $testState = New-PesterState -Path $root
    
                # Path deliberately duplicated to make sure the code doesn't produce multiple breakpoints for the same commands
                Enter-CoverageAnalysis -CodeCoverage "$root\TestScript.ps1", "$root\TestScript.ps1" -PesterState $testState
    
                It 'Has the proper number of breakpoints defined' {
                    $testState.CommandCoverage.Count | Should Be 7
                }
    
                $null = & "$root\TestScript.ps1"
                $coverageReport = Get-CoverageReport -PesterState $testState
    
                It 'Reports the proper number of executed commands' {
                    $coverageReport.NumberOfCommandsExecuted | Should Be 6
                }
    
                It 'Reports the proper number of analyzed commands' {
                    $coverageReport.NumberOfCommandsAnalyzed | Should Be 7
                }
    
                It 'Reports the proper number of analyzed files' {
                    $coverageReport.NumberOfFilesAnalyzed | Should Be 1
                }
    
                It 'Reports the proper number of missed commands' {
                    $coverageReport.MissedCommands.Count | Should Be 1
                }
    
                It 'Reports the correct missed command' {
                    $coverageReport.MissedCommands[0].Command | Should Be "'I am function two.  I never get called.'"
                }
    
                It 'Reports the proper number of hit commands' {
                    $coverageReport.HitCommands.Count | Should Be 6
                }
    
                It 'Reports the correct hit command' {
                    $coverageReport.HitCommands[0].Command | Should Be "'I am the nested function.'"
                }
    
                Exit-CoverageAnalysis -PesterState $testState
            }
    
            Context 'Single function with missed commands' {
                $testState = New-PesterState -Path $root
    
                Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\TestScript.ps1"; Function = 'FunctionTwo'} -PesterState $testState
    
                It 'Has the proper number of breakpoints defined' {
                    $testState.CommandCoverage.Count | Should Be 1
                }
    
                $null = & "$root\TestScript.ps1"
                $coverageReport = Get-CoverageReport -PesterState $testState
    
                It 'Reports the proper number of executed commands' {
                    $coverageReport.NumberOfCommandsExecuted | Should Be 0
                }
    
                It 'Reports the proper number of analyzed commands' {
                    $coverageReport.NumberOfCommandsAnalyzed | Should Be 1
                }
    
                It 'Reports the proper number of missed commands' {
                    $coverageReport.MissedCommands.Count | Should Be 1
                }
    
                It 'Reports the correct missed command' {
                    $coverageReport.MissedCommands[0].Command | Should Be "'I am function two.  I never get called.'"
                }
    
                It 'Reports the proper number of hit commands' {
                    $coverageReport.HitCommands.Count | Should Be 0
                }
    
                Exit-CoverageAnalysis -PesterState $testState
            }
    
            Context 'Single function with no missed commands' {
                $testState = New-PesterState -Path $root
    
                Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\TestScript.ps1"; Function = 'FunctionOne'} -PesterState $testState
    
                It 'Has the proper number of breakpoints defined' {
                    $testState.CommandCoverage.Count | Should Be 5
                }
    
                $null = & "$root\TestScript.ps1"
                $coverageReport = Get-CoverageReport -PesterState $testState
    
                It 'Reports the proper number of executed commands' {
                    $coverageReport.NumberOfCommandsExecuted | Should Be 5
                }
    
                It 'Reports the proper number of analyzed commands' {
                    $coverageReport.NumberOfCommandsAnalyzed | Should Be 5
                }
    
                It 'Reports the proper number of missed commands' {
                    $coverageReport.MissedCommands.Count | Should Be 0
                }
    
                It 'Reports the proper number of hit commands' {
                    $coverageReport.HitCommands.Count | Should Be 5
                }
    
                It 'Reports the correct hit command' {
                    $coverageReport.HitCommands[0].Command | Should Be "'I am the nested function.'"
                }
    
                Exit-CoverageAnalysis -PesterState $testState
            }
    
            Context 'Range of lines' {
                $testState = New-PesterState -Path $root
    
                Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\TestScript.ps1"; StartLine = 11; EndLine = 12 } -PesterState $testState
    
                It 'Has the proper number of breakpoints defined' {
                    $testState.CommandCoverage.Count | Should Be 2
                }
    
                $null = & "$root\TestScript.ps1"
                $coverageReport = Get-CoverageReport -PesterState $testState
    
                It 'Reports the proper number of executed commands' {
                    $coverageReport.NumberOfCommandsExecuted | Should Be 2
                }
    
                It 'Reports the proper number of analyzed commands' {
                    $coverageReport.NumberOfCommandsAnalyzed | Should Be 2
                }
    
                It 'Reports the proper number of missed commands' {
                    $coverageReport.MissedCommands.Count | Should Be 0
                }
    
                It 'Reports the proper number of hit commands' {
                    $coverageReport.HitCommands.Count | Should Be 2
                }
    
                It 'Reports the correct hit command' {
                    $coverageReport.HitCommands[0].Command | Should Be "'I am functionOne'"
                }
    
                Exit-CoverageAnalysis -PesterState $testState
            }
    
            Context 'Wildcard resolution' {
                $testState = New-PesterState -Path $root
    
                Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\*.ps1"; Function = '*' } -PesterState $testState
    
                It 'Has the proper number of breakpoints defined' {
                    $testState.CommandCoverage.Count | Should Be 6
                }
    
                $null = & "$root\TestScript.ps1"
                $coverageReport = Get-CoverageReport -PesterState $testState
    
                It 'Reports the proper number of executed commands' {
                    $coverageReport.NumberOfCommandsExecuted | Should Be 5
                }
    
                It 'Reports the proper number of analyzed commands' {
                    $coverageReport.NumberOfCommandsAnalyzed | Should Be 6
                }
    
                It 'Reports the proper number of analyzed files' {
                    $coverageReport.NumberOfFilesAnalyzed | Should Be 1
                }
    
                It 'Reports the proper number of missed commands' {
                    $coverageReport.MissedCommands.Count | Should Be 1
                }
    
                It 'Reports the correct missed command' {
                    $coverageReport.MissedCommands[0].Command | Should Be "'I am function two.  I never get called.'"
                }
    
                It 'Reports the proper number of hit commands' {
                    $coverageReport.HitCommands.Count | Should Be 5
                }
    
                It 'Reports the correct hit command' {
                    $coverageReport.HitCommands[0].Command | Should Be "'I am the nested function.'"
                }
    
                Exit-CoverageAnalysis -PesterState $testState
            }
        }
    
        Describe 'Stripping common parent paths' {
            $paths = @(
                Normalize-Path 'C:\Common\Folder\UniqueSubfolder1/File.ps1'
                Normalize-Path 'C:\Common\Folder\UniqueSubfolder2/File2.ps1'
                Normalize-Path 'C:\Common\Folder\UniqueSubfolder3/File3.ps1'
            )
    
            $commonPath = Get-CommonParentPath -Path $paths
            $expectedCommonPath = Normalize-Path 'C:\Common/Folder'
    
            It 'Identifies the correct parent path' {
                $commonPath | Should Be $expectedCommonPath
            }
    
            $expectedRelativePath = Normalize-Path 'UniqueSubfolder1/File.ps1'
            $relativePath = Get-RelativePath -Path $paths[0] -RelativeTo $commonPath
    
            It 'Strips the common path correctly' {
                $relativePath | Should Be $expectedRelativePath
            }
        }
    
        if ((Get-Module -ListAvailable PSDesiredStateConfiguration) -and $PSVersionTable.PSVersion.Major -ge 4)
        {
            Describe 'Analyzing coverage of a DSC configuration' {
                $root = (Get-PSDrive TestDrive).Root
    
                $null = New-Item -Path $root\TestScriptWithConfiguration.ps1 -ItemType File -ErrorAction SilentlyContinue
    
                Set-Content -Path $root\TestScriptWithConfiguration.ps1 -Value @'
                    $line1 = $true   # Triggers breakpoint
                    $line2 = $true   # Triggers breakpoint
    
                    configuration MyTestConfig   # does NOT trigger breakpoint
                    {
                        Import-DscResource -ModuleName PSDesiredStateConfiguration # Triggers breakpoint in PowerShell v5 but not in v4
    
                        Node localhost    # Triggers breakpoint
                        {
                            WindowsFeature XPSViewer   # Triggers breakpoint
                            {
                                Name = 'XPS-Viewer'  # does NOT trigger breakpoint
                                Ensure = 'Present'   # does NOT trigger breakpoint
                            }
                        }
    
                        return # does NOT trigger breakpoint
    
                        $doesNotExecute = $true   # Triggers breakpoint
                    }
    
                    $line3 = $true   # Triggers breakpoint
    
                    return   # does NOT trigger breakpoint
    
                    $doesnotexecute = $true   # Triggers breakpoint
    '@
    
                $testState = New-PesterState -Path $root
    
                Enter-CoverageAnalysis -CodeCoverage "$root\TestScriptWithConfiguration.ps1" -PesterState $testState
    
                #the AST does not parse Import-DscResource -ModuleName PSDesiredStateConfiguration on PowerShell 4
                $runsInPowerShell4 = $PSVersionTable.PSVersion.Major -eq 4
                It 'Has the proper number of breakpoints defined' {
                    if($runsInPowerShell4) { $expected = 7 } else { $expected = 8 }
    
                    $testState.CommandCoverage.Count | Should Be $expected
                }
    
                $null = . "$root\TestScriptWithConfiguration.ps1"
    
                $coverageReport = Get-CoverageReport -PesterState $testState
                It 'Reports the proper number of missed commands before running the configuration' {
                    if($runsInPowerShell4) { $expected = 4 } else { $expected = 5 }
    
                    $coverageReport.MissedCommands.Count | Should Be $expected
                }
    
                MyTestConfig -OutputPath $root
    
                $coverageReport = Get-CoverageReport -PesterState $testState
                It 'Reports the proper number of missed commands after running the configuration' {
                    if($runsInPowerShell4) { $expected = 2 } else { $expected = 3 }
    
                    $coverageReport.MissedCommands.Count | Should Be $expected
                }
    
                Exit-CoverageAnalysis -PesterState $testState
            }
        }
    }
    
  • tools\Functions\Describe.ps1 Show
    function Describe {
    <#
    .SYNOPSIS
    Creates a logical group of tests.
    
    .DESCRIPTION
    Creates a logical group of tests.  All Mocks and TestDrive contents
    defined within a Describe block are scoped to that Describe; they
    will no longer be present when the Describe block exits.  A Describe
    block may contain any number of Context and It blocks.
    
    .PARAMETER Name
    The name of the test group. This is often an expressive phrase describing
    the scenario being tested.
    
    .PARAMETER Fixture
    The actual test script. If you are following the AAA pattern (Arrange-Act-Assert),
    this typically holds the arrange and act sections. The Asserts will also lie
    in this block but are typically nested each in its own It block. Assertions are
    typically performed by the Should command within the It blocks.
    
    
    .PARAMETER Tag
    Optional parameter containing an array of strings.  When calling Invoke-Pester,
    it is possible to specify a -Tag parameter which will only execute Describe blocks
    containing the same Tag.
    
    .EXAMPLE
    function Add-Numbers($a, $b) {
        return $a + $b
    }
    
    Describe "Add-Numbers" {
        It "adds positive numbers" {
            $sum = Add-Numbers 2 3
            $sum | Should Be 5
        }
    
        It "adds negative numbers" {
            $sum = Add-Numbers (-2) (-2)
            $sum | Should Be (-4)
        }
    
        It "adds one negative number to positive number" {
            $sum = Add-Numbers (-2) 2
            $sum | Should Be 0
        }
    
        It "concatenates strings if given strings" {
            $sum = Add-Numbers two three
            $sum | Should Be "twothree"
        }
    }
    
    .LINK
    It
    Context
    Invoke-Pester
    about_Should
    about_Mocking
    about_TestDrive
    
    #>
    
        param(
            [Parameter(Mandatory = $true, Position = 0)]
            [string] $Name,
    
            [Alias('Tags')]
            [string[]] [email protected](),
    
            [Parameter(Position = 1)]
            [ValidateNotNull()]
            [ScriptBlock] $Fixture = $(Throw "No test script block is provided. (Have you put the open curly brace on the next line?)"),
    
            [string] $CommandUsed = 'Describe'
        )
    
        if ($null -eq (& $SafeCommands['Get-Variable'] -Name Pester -ValueOnly -ErrorAction $script:IgnoreErrorPreference))
        {
            # User has executed a test script directly instead of calling Invoke-Pester
            $Pester = New-PesterState -Path (& $SafeCommands['Resolve-Path'] .) -TestNameFilter $null -TagFilter @() -SessionState $PSCmdlet.SessionState
            $script:mockTable = @{}
        }
    
        DescribeImpl @PSBoundParameters -Pester $Pester -DescribeOutputBlock ${function:Write-Describe} -TestOutputBlock ${function:Write-PesterResult}
    }
    
    function DescribeImpl {
        param(
            [Parameter(Mandatory = $true, Position = 0)]
            [string] $Name,
    
            [Alias('Tags')]
            [email protected](),
    
            [Parameter(Position = 1)]
            [ValidateNotNull()]
            [ScriptBlock] $Fixture = $(Throw "No test script block is provided. (Have you put the open curly brace on the next line?)"),
    
            [string] $CommandUsed = 'Describe',
    
            $Pester,
    
            [scriptblock] $DescribeOutputBlock,
    
            [scriptblock] $TestOutputBlock,
    
            [switch] $NoTestDrive
        )
    
        Assert-DescribeInProgress -CommandName $CommandUsed
    
        if ($Pester.TestGroupStack.Count -eq 2)
        {
            if($Pester.TestNameFilter-and -not ($Pester.TestNameFilter | & $SafeCommands['Where-Object'] { $Name -like $_ }))
            {
                #skip this test
                return
            }
    
            if($Pester.TagFilter -and @(& $SafeCommands['Compare-Object'] $Tag $Pester.TagFilter -IncludeEqual -ExcludeDifferent).count -eq 0) {return}
            if($Pester.ExcludeTagFilter -and @(& $SafeCommands['Compare-Object'] $Tag $Pester.ExcludeTagFilter -IncludeEqual -ExcludeDifferent).count -gt 0) {return}
        }
        else
        {
            if ($PSBoundParameters.ContainsKey('Tag'))
            {
                Write-Warning "${CommandUsed} '$Name': Tags are only effective on the outermost test group, for now."
            }
        }
    
        $Pester.EnterTestGroup($Name, $CommandUsed)
    
        if ($null -ne $DescribeOutputBlock)
        {
            & $DescribeOutputBlock $Name $CommandUsed
        }
    
        $testDriveAdded = $false
        try
        {
            if (-not $NoTestDrive)
            {
                if (-not (Test-Path TestDrive:\))
                {
                    New-TestDrive
                    $testDriveAdded = $true
                }
                else
                {
                    $TestDriveContent = Get-TestDriveChildItem
                }
            }
    
            Add-SetupAndTeardown -ScriptBlock $Fixture
            Invoke-TestGroupSetupBlocks
    
            do
            {
                $null = & $Fixture
            } until ($true)
        }
        catch
        {
            $firstStackTraceLine = $_.InvocationInfo.PositionMessage.Trim() -split '\r?\n' | & $SafeCommands['Select-Object'] -First 1
            $Pester.AddTestResult("Error occurred in $CommandUsed block", "Failed", $null, $_.Exception.Message, $firstStackTraceLine, $null, $null, $_)
            if ($null -ne $TestOutputBlock)
            {
                & $TestOutputBlock $Pester.TestResult[-1]
            }
        }
        finally
        {
            Invoke-TestGroupTeardownBlocks
            if (-not $NoTestDrive)
            {
                if ($testDriveAdded)
                {
                    Remove-TestDrive
                }
                else
                {
                    Clear-TestDrive -Exclude ($TestDriveContent | & $SafeCommands['Select-Object'] -ExpandProperty FullName)
                }
            }
        }
    
        Exit-MockScope
    
        $Pester.LeaveTestGroup($Name, $CommandUsed)
    }
    
    # Name is now misleading; rename later.  (Many files touched to change this.)
    function Assert-DescribeInProgress
    {
        param ($CommandName)
        if ($null -eq $Pester)
        {
            throw "The $CommandName command may only be used from a Pester test script."
        }
    }
    
  • tools\Functions\Describe.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    Describe 'Testing Describe' {
        It 'Has a non-mandatory fixture parameter which throws the proper error message if missing' {
            $command = Get-Command Describe -Module Pester
            $command | Should Not Be $null
    
            $parameter = $command.Parameters['Fixture']
            $parameter | Should Not Be $null
    
            # Some environments (Nano/CoreClr) don't have all the type extensions
            $attribute = $parameter.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] }
            $isMandatory = $null -ne $attribute -and $attribute.Mandatory
    
            $isMandatory | Should Be $false
    
            { Describe Bogus } | Should Throw 'No test script block is provided'
        }
    }
    
    InModuleScope Pester {
        Describe 'Describe - Implementation' {
            # Function / mock used for call history tracking and assertion purposes only.
            function MockMe { param ($Name) }
            Mock MockMe
    
            Context 'Handling errors in the Fixture' {
                $testState = New-PesterState -Path $TestDrive
    
                # This is necessary for now, Describe code assumes that filters should only apply at a stack depth of
                # "2".  ("1" being the Tests.ps1 script that's active.)
                $testState.EnterTestGroup('Mocked Script', 'Script')
    
                $blockWithError = {
                    throw 'Bad stuff happened!'
                    MockMe
                }
    
                It 'Does not rethrow terminating exceptions from the Fixture block' {
                    { DescribeImpl -Pester $testState -Name 'A test' -Fixture $blockWithError -NoTestDrive } | Should Not Throw
                }
    
                It 'Adds a failed test result when errors occur in the Describe block' {
                    $testState.TestResult.Count | Should Not Be 0
                    $testState.TestResult[-1].Passed | Should Be $false
                    $testState.TestResult[-1].Name | Should Be 'Error occurred in Describe block'
                    $testState.TestResult[-1].FailureMessage | Should Be 'Bad stuff happened!'
                }
    
                It 'Does not attempt to run the rest of the Describe block after the error occurs' {
                    Assert-MockCalled MockMe -Scope Context -Exactly -Times 0
                }
            }
    
            Context 'Calls to the output blocks' {
                $testState = New-PesterState -Path $TestDrive
    
                # This is necessary for now, Describe code assumes that filters should only apply at a stack depth of
                # "2".  ("1" being the Tests.ps1 script that's active.)
                $testState.EnterTestGroup('Mocked Script', 'Script')
    
                $describeOutput = { MockMe -Name Describe }
                $testOutput = { MockMe -Name Test }
    
                It 'Calls the Describe output block once, and does not call the test output block when no errors occur' {
                    $block = { $null = $null }
    
                    DescribeImpl -Pester $testState -Name 'A test' -Fixture $block -DescribeOutputBlock $describeOutput -TestOutputBlock $testOutput -NoTestDrive
    
                    Assert-MockCalled MockMe -Exactly 0 -ParameterFilter { $Name -eq 'Test' } -Scope It
                    Assert-MockCalled MockMe -Exactly 1 -ParameterFilter { $Name -eq 'Describe' } -Scope It
                }
    
                It 'Calls the Describe output block once, and the test output block once if an error occurs.' {
                    $block = { throw 'up' }
    
                    DescribeImpl -Pester $testState -Name 'A test' -Fixture $block -DescribeOutputBlock $describeOutput -TestOutputBlock $testOutput -NoTestDrive
    
                    Assert-MockCalled MockMe -Exactly 1 -ParameterFilter { $Name -eq 'Test' } -Scope It
                    Assert-MockCalled MockMe -Exactly 1 -ParameterFilter { $Name -eq 'Describe' } -Scope It
                }
            }
    
            Context 'Test Name Filter' {
                $testState = New-PesterState -Path $TestDrive -TestNameFilter '*One*', 'Test Two'
    
                # This is necessary for now, Describe code assumes that filters should only apply at a stack depth of
                # "2".  ("1" being the Tests.ps1 script that's active.)
                $testState.EnterTestGroup('Mocked Script', 'Script')
    
                $testBlock = { MockMe }
    
                $cases = @(
                    @{ Name = 'TestOneTest'; Description = 'matches a wildcard' }
                    @{ Name = 'Test Two';    Description = 'matches exactly' }
                    @{ Name = 'test two';    Description = 'matches ignoring case' }
                )
    
                It -TestCases $cases 'Calls the test block when the test name <Description>' {
                    param ($Name)
                    DescribeImpl -Name $Name -Pester $testState -Fixture $testBlock -NoTestDrive
                    Assert-MockCalled MockMe -Scope It -Exactly 1
                }
    
                It 'Does not call the test block when the test name doesn''t match a filter' {
                    DescribeImpl -Name 'Test On' -Pester $testState -Fixture $testBlock -NoTestDrive
                    DescribeImpl -Name 'Two' -Pester $testState -Fixture $testBlock -NoTestDrive
                    DescribeImpl -Name 'Bogus' -Pester $testState -Fixture $testBlock -NoTestDrive
    
                    Assert-MockCalled MockMe -Scope It -Exactly 0
                }
            }
    
            Context 'Tags Filter' {
                $testState = New-PesterState -Path $TestDrive -TagFilter 'One', '*Two*'
    
                # This is necessary for now, Describe code assumes that filters should only apply at a stack depth of
                # "2".  ("1" being the Tests.ps1 script that's active.)
                $testState.EnterTestGroup('Mocked Script', 'Script')
    
                $testBlock = { MockMe }
    
                $cases = @(
                    @{ Tags = 'One';         Description = 'matches the first tag exactly' }
                    @{ Tags = '*Two*';       Description = 'matches the second tag exactly' }
                    @{ Tags = 'One', '*Two'; Description = 'matches both tags exactly' }
                    @{ Tags = 'one';         Description = 'matches the first tag ignoring case' }
                    @{ Tags = '*two*';       Description = 'matches the second tag ignoring case' }
                )
    
                It -TestCases $cases 'Calls the test block when the tag filter <Description>' {
                    param ($Tags)
    
                    DescribeImpl -Name 'Blah' -Tags $Tags -Pester $testState -Fixture $testBlock -NoTestDrive
                    Assert-MockCalled MockMe -Scope It -Exactly 1
                }
    
                It 'Does not call the test block when the test tags don''t match the pester state''s tags.' {
                    # Unlike the test name filter, tags are literal matches and not interpreted as wildcards.
                    DescribeImpl -Name 'Blah' -Tags 'TestTwoTest' -Pester $testState -Fixture $testBlock -NoTestDrive
    
                    Assert-MockCalled MockMe -Scope It -Exactly 0
                }
            }
    
            # Testing nested Describe is probably not necessary here; that's covered by PesterState.Tests.ps1 and $pester.EnterDescribe().
        }
    }
    
  • tools\Functions\Gherkin.ps1 Show
    if ($PSVersionTable.PSVersion.Major -le 2) { return }
    
    Add-Type -Path "${Script:PesterRoot}\lib\Gherkin.dll"
    
    $StepPrefix = "Gherkin-Step "
    $GherkinSteps = @{}
    $GherkinHooks = @{
        BeforeAllFeatures = @()
        BeforeFeature = @()
        BeforeScenario = @()
        BeforeStep = @()
        AfterAllFeatures = @()
        AfterFeature = @()
        AfterScenario = @()
        AfterStep = @()
    }
    
    function Invoke-GherkinHook {
        [CmdletBinding()]
        param([string]$Hook, [string]$Name, [string[]]$Tags)
    
        if($GherkinHooks.${Hook}) {
            foreach($GherkinHook in $GherkinHooks.${Hook}) {
                if($GherkinHook.Tags -and $Tags) {
                    :tags foreach($hookTag in $GherkinHook.Tags) {
                        foreach($testTag in $Tags) {
                            if($testTag -match "^($hookTag)$") {
                                & $hook.Script $Name
                                break :tags
                            }
                        }
                    }
                } elseif($GherkinHook.Tags) {
                    # If the hook has tags, it can't run if the step doesn't
                } else {
                    & $GherkinHook.Script $Name
                }
            } # @{ Tags = $Tags; Script = $Test }
        }
    }
    
    function Invoke-Gherkin {
        <#
            .Synopsis
                Invokes Pester to run all tests defined in .feature files
            .Description
                Upon calling Invoke-Gherkin, all files that have a name matching *.feature in the current folder (and child folders recursively), will be parsed and executed.
    
                If ScenarioName is specified, only scenarios which match the provided name(s) will be run. If FailedLast is specified, only scenarios which failed the previous run will be re-executed.
    
                Optionally, Pester can generate a report of how much code is covered by the tests, and information about any commands which were not executed.
            .Example
                Invoke-Gherkin
    
                This will find all *.feature specifications and execute their tests. No exit code will be returned and no log file will be saved.
    
            .Example
                Invoke-Gherkin -Path ./tests/Utils*
    
                This will run all *.feature specifications under ./Tests that begin with Utils.
    
            .Example
                Invoke-Gherkin -ScenarioName "Add Numbers"
    
                This will only run the Scenario named "Add Numbers"
    
            .Example
                Invoke-Gherkin -EnableExit -OutputXml "./artifacts/TestResults.xml"
    
                This runs all tests from the current directory downwards and writes the results according to the NUnit schema to artifatcs/TestResults.xml just below the current directory. The test run will return an exit code equal to the number of test failures.
    
            .Example
                Invoke-Gherkin -CodeCoverage 'ScriptUnderTest.ps1'
    
                Runs all *.feature specifications in the current directory, and generates a coverage report for all commands in the "ScriptUnderTest.ps1" file.
    
            .Example
                Invoke-Gherkin -CodeCoverage @{ Path = 'ScriptUnderTest.ps1'; Function = 'FunctionUnderTest' }
    
                Runs all *.feature specifications in the current directory, and generates a coverage report for all commands in the "FunctionUnderTest" function in the "ScriptUnderTest.ps1" file.
    
            .Example
                Invoke-Gherkin -CodeCoverage @{ Path = 'ScriptUnderTest.ps1'; StartLine = 10; EndLine = 20 }
    
                Runs all *.feature specifications in the current directory, and generates a coverage report for all commands on lines 10 through 20 in the "ScriptUnderTest.ps1" file.
    
            .Link
                Invoke-Pester
        #>
        [CmdletBinding(DefaultParameterSetName = 'Default')]
        param(
            # Rerun only the scenarios which failed last time
            [Parameter(Mandatory = $True, ParameterSetName = "RetestFailed")]
            [switch]$FailedLast,
    
            # This parameter indicates which feature files should be tested.
            # Aliased to 'Script' for compatibility with Pester, but does not support hashtables, since feature files don't take parameters.
            [Parameter(Position=0,Mandatory=0)]
            [Alias('Script','relative_path')]
            [string]$Path = $Pwd,
    
            # When set, invokes testing of scenarios which match this name.
            # Aliased to 'Name' and 'TestName' for compatibility with Pester.
            [Parameter(Position=1,Mandatory=0)]
            [Alias("Name","TestName")]
            [string[]]$ScenarioName,
    
            # Will cause Invoke-Gherkin to exit with a exit code equal to the number of failed tests once all tests have been run. Use this to "fail" a build when any tests fail.
            [Parameter(Position=2,Mandatory=0)]
            [switch]$EnableExit,
    
            # Filters Scenarios and Features and runs only the ones tagged with the specified tags.
            [Parameter(Position=4,Mandatory=0)]
            [Alias('Tags')]
            [string[]]$Tag,
    
            # Informs Invoke-Pester to not run blocks tagged with the tags specified.
            [string[]]$ExcludeTag,
    
            # Instructs Pester to generate a code coverage report in addition to running tests.  You may pass either hashtables or strings to this parameter.
            # If strings are used, they must be paths (wildcards allowed) to source files, and all commands in the files are analyzed for code coverage.
            # By passing hashtables instead, you can limit the analysis to specific lines or functions within a file.
            # Hashtables must contain a Path key (which can be abbreviated to just "P"), and may contain Function (or "F"), StartLine (or "S"), and EndLine ("E") keys to narrow down the commands to be analyzed.
            # If Function is specified, StartLine and EndLine are ignored.
            # If only StartLine is defined, the entire script file starting with StartLine is analyzed.
            # If only EndLine is present, all lines in the script file up to and including EndLine are analyzed.
            # Both Function and Path (as well as simple strings passed instead of hashtables) may contain wildcards.
            [object[]] $CodeCoverage = @(),
    
            # Makes Pending and Skipped tests to Failed tests. Useful for continuous integration where you need to make sure all tests passed.
            [Switch]$Strict,
    
            # The path to write a report file to. If this path is not provided, no log will be generated.
            [string] $OutputFile,
    
            # The format for output (LegacyNUnitXml or NUnitXml), defaults to NUnitXml
            [ValidateSet('NUnitXml')]
            [string] $OutputFormat = 'NUnitXml',
    
            # Disables the output Pester writes to screen. No other output is generated unless you specify PassThru, or one of the Output parameters.
            [Switch]$Quiet,
    
            [switch]$PassThru
        )
        begin {
            Import-LocalizedData -BindingVariable Script:ReportStrings -BaseDirectory $PesterRoot -FileName Gherkin.psd1
    
            # Make sure broken tests don't leave you in space:
            $Location = Get-Location
            $FileLocation = Get-Location -PSProvider FileSystem
    
            $script:GherkinSteps = @{}
            $script:GherkinHooks = @{
                BeforeAllFeatures = @()
                BeforeFeature = @()
                BeforeScenario = @()
                BeforeStep = @()
                AfterAllFeatures = @()
                AfterFeature = @()
                AfterScenario = @()
                AfterStep = @()
            }
    
        }
        end {
    
            if($PSCmdlet.ParameterSetName -eq "RetestFailed") {
                if((Test-Path variable:script:pester) -and $pester.FailedScenarios.Count -gt 0 ) {
                    $ScenarioName = $Pester.FailedScenarios | Select-Object -Expand Name
                }
                else {
                    throw "There's no existing failed tests to re-run"
                }
            }
    
            # Clear mocks
            $script:mockTable = @{}
    
            $pester = New-PesterState -TestNameFilter $ScenarioName -TagFilter @($Tag -split "\s+") -ExcludeTagFilter ($ExcludeTag -split "\s") -SessionState $PSCmdlet.SessionState -Strict:$Strict -Quiet:$Quiet |
                Add-Member -MemberType NoteProperty -Name Features -Value (New-Object System.Collections.Generic.List[Gherkin.Ast.Feature]) -PassThru |
                Add-Member -MemberType ScriptProperty -Name FailedScenarios -Value {
                    $Names = $this.TestResult | Group Context | Where { $_.Group | Where { -not $_.Passed } } | Select-Object -Expand Name
                    $this.Features.Scenarios | Where { $Names -contains $_.Name }
                } -PassThru |
                Add-Member -MemberType ScriptProperty -Name PassedScenarios -Value {
                    $Names = $this.TestResult | Group Context | Where { -not ($_.Group | Where { -not $_.Passed }) } | Select-Object -Expand Name
                    $this.Features.Scenarios | Where { $Names -contains $_.Name }
                } -PassThru
    
            Write-PesterStart $pester $Path
    
            Enter-CoverageAnalysis -CodeCoverage $CodeCoverage -PesterState $pester
    
            $parser = New-Object Gherkin.Parser
            $BeforeAllFeatures = $false
            foreach($FeatureFile in Get-ChildItem $Path -Filter "*.feature" -Recurse ) {
    
                # Remove all the steps
                $Script:GherkinSteps.Clear()
                # Import all the steps that are at the same level or a subdirectory
                $StepPath = Split-Path $FeatureFile.FullName
                $StepFiles = Get-ChildItem $StepPath -Filter "*.steps.ps1" -Recurse
                foreach($StepFile in $StepFiles){
                    . $StepFile.FullName
                }
                Write-Verbose "Loaded $($Script:GherkinSteps.Count) step definitions from $(@($StepFiles).Count) steps file(s)"
    
                if(!$BeforeAllFeatures) {
                    Invoke-GherkinHook BeforeAllFeatures
                    $BeforeAllFeatures = $true
                }
    
                try {
                    $Feature = $parser.Parse($FeatureFile.FullName).Feature | Convert-Tags
                } catch [Gherkin.ParserException] {
                    Write-Warning "Skipped '$($FeatureFile.FullName)' because of parser errors:`n$(($_.Exception.Errors | Select-Object -Expand Message) -join "`n`n")"
                    continue
                }
    
                $null = $Pester.Features.Add($Feature)
    
                ## This is Pesters "Describe" function
                $Pester.EnterTestGroup($Feature.Name, 'Describe')
                New-TestDrive
    
                Invoke-GherkinHook BeforeFeature $Feature.Name $Feature.Tags
                ## Hypothetically, we could add FEATURE setup/teardown?
                # Add-SetupAndTeardown -ScriptBlock $Fixture
                # Invoke-TestGroupSetupBlocks -Scope $pester.Scope
    
                $Background = $null
                $Scenarios = foreach($Scenario in $Feature.Children) {
                                switch($Scenario.Keyword.Trim())
                                {
                                    "Scenario" {
                                        $Scenario = Convert-Tags -Input $Scenario -BaseTags $Feature.Tags
                                    }
                                    "Scenario Outline" {
                                        $Scenario = Convert-Tags -Input $Scenario -BaseTags $Feature.Tags
                                    }
                                    "Background" {
                                        $Background = Convert-Tags -Input $Scenario -BaseTags $Feature.Tags
                                        continue
                                    }
                                    default {
                                        Write-Warning "Unexpected Feature Child: $_"
                                    }
                                }
    
                                if($Scenario.Examples) {
                                    foreach($ExampleSet in $Scenario.Examples) {
                                        $Names = @($ExampleSet.TableHeader.Cells | Select -Expand Value)
                                        $NamesPattern = "<(?:" + ($Names -join "|") + ")>"
                                        $Steps = foreach($Example in $ExampleSet.TableBody) {
                                                    foreach ($Step in $Scenario.Steps) {
                                                        [string]$StepText = $Step.Text
                                                        $StepArgument = $Step.Argument
                                                        if($StepText -match $NamesPattern) {
                                                            for($n = 0; $n -lt $Names.Length; $n++) {
                                                                $Name = $Names[$n]
                                                                if($Example.Cells[$n].Value -and $StepText -match "<${Name}>") {
                                                                    $StepText = $StepText -replace "<${Name}>", $Example.Cells[$n].Value
                                                                }
                                                            }
                                                        }
                                                        if($StepText -ne $Step.Text) {
                                                            New-Object Gherkin.Ast.Step $Step.Location, $Step.Keyword.Trim(), $StepText, $Step.Argument
                                                        } else {
                                                            $Step
                                                        }
                                                    }
                                                }
                                        $ScenarioName = $Scenario.Name
                                        if($ExampleSet.Name) {
                                            $ScenarioName = $ScenarioName + "`n  Examples:" + $ExampleSet.Name.Trim()
                                        }
                                        New-Object Gherkin.Ast.Scenario $ExampleSet.Tags, $Scenario.Location, $Scenario.Keyword.Trim(), $ScenarioName, $Scenario.Description, $Steps | Convert-Tags $Scenario.Tags
                                    }
                                } else {
                                    $Scenario
                                }
                            }
                Add-Member -Input $Feature -Type NoteProperty -Name Scenarios -Value $Scenarios -Force
    
                # Move the test name filter first, since it'll likely return only a single item
                if($pester.TestNameFilter) {
                    $Scenarios = foreach($nameFilter in $pester.TestNameFilter) {
                        $Scenarios | Where { $_.Name -like $NameFilter }
                    }
                    $Scenarios = $Scenarios | Get-Unique
                }
    
                # if($Pester.TagFilter -and @(Compare-Object $Tags $Pester.TagFilter -IncludeEqual -ExcludeDifferent).count -eq 0) {return}
                if($pester.TagFilter) {
                    $Scenarios = $Scenarios | Where { Compare-Object $_.Tags $pester.TagFilter -IncludeEqual -ExcludeDifferent }
                }
    
                # if($Pester.ExcludeTagFilter -and @(Compare-Object $Tags $Pester.ExcludeTagFilter -IncludeEqual -ExcludeDifferent).count -gt 0) {return}
                if($Pester.ExcludeTagFilter) {
                    $Scenarios = $Scenarios | Where { !(Compare-Object $_.Tags $Pester.ExcludeTagFilter -IncludeEqual -ExcludeDifferent) }
                }
    
                if($Scenarios -and !$Quiet) {
                    Write-Describe $Feature
                }
    
                foreach($Scenario in $Scenarios) {
                    # This is Pester's Context function
                    $Pester.EnterTestGroup($Scenario.Name, 'Context')
                    $TestDriveContent = Get-TestDriveChildItem
    
                    Invoke-GherkinScenario $Pester $Scenario $Background -Quiet:$Quiet
    
                    Clear-TestDrive -Exclude ($TestDriveContent | select -ExpandProperty FullName)
                    # Exit-MockScope
                    $Pester.LeaveTestGroup($Scenario.Name, 'Context')
                }
    
                ## This is Pesters "Describe" function again
                Invoke-GherkinHook AfterFeature $Feature.Name $Feature.Tags
    
                Remove-TestDrive
                ## Hypothetically, we could add FEATURE setup/teardown?
                # Clear-SetupAndTeardown
                Exit-MockScope
                $Pester.LeaveTestGroup($Feature.Name, 'Describe')
            }
            Invoke-GherkinHook AfterAllFeatures
    
            # Remove all the steps
            $Script:GherkinSteps.Clear()
    
            $Location | Set-Location
            [Environment]::CurrentDirectory = Convert-Path $FileLocation
    
            $pester | Write-PesterReport
            $coverageReport = Get-CoverageReport -PesterState $pester
            Write-CoverageReport -CoverageReport $coverageReport
            Exit-CoverageAnalysis -PesterState $pester
    
            if(Get-Variable -Name OutputFile -ValueOnly -ErrorAction $script:IgnoreErrorPreference) {
                Export-PesterResults -PesterState $pester -Path $OutputFile -Format $OutputFormat
            }
    
            if ($PassThru) {
                # Remove all runtime properties like current* and Scope
                $properties = @(
                    "Path","TagFilter","TestNameFilter","TotalCount","PassedCount","FailedCount","Time","TestResult","PassedScenarios","FailedScenarios"
    
                    if ($CodeCoverage)
                    {
                        @{ Name = 'CodeCoverage'; Expression = { $coverageReport } }
                    }
                )
                $pester | Select -Property $properties
            }
            if ($EnableExit) { Exit-WithCode -FailedCount $pester.FailedCount }
        }
    }
    
    function Invoke-GherkinScenario {
        [CmdletBinding()]
        param(
            $Pester, $Scenario, $Background, [Switch]$Quiet
        )
    
        if(!$Quiet) { Write-Context $Scenario }
        # If there's a background, we have to run that before the actual tests
        if($Background) {
            Invoke-GherkinScenario $Pester $Background -Quiet
        }
    
        Invoke-GherkinHook BeforeScenario $Scenario.Name $Scenario.Tags
    
        foreach($Step in $Scenario.Steps) {
            Invoke-GherkinStep $Pester $Step -Quiet:$Quiet
        }
    
        Invoke-GherkinHook AfterScenario $Scenario.Name $Scenario.Tags
    }
    
    
    function Invoke-GherkinStep {
        [CmdletBinding()]
        param (
            $Pester, $Step, [Switch]$Quiet
        )
        #  Pick the match with the least grouping wildcards in it...
        $StepCommand = $(
            foreach($StepCommand in $Script:GherkinSteps.Keys) {
                if($Step.Text -match "^${StepCommand}$") {
                    $StepCommand | Add-Member MatchCount $Matches.Count -PassThru
                }
            }
        ) | Sort MatchCount | Select -First 1
        $StepText = "{0} {1}" -f $Step.Keyword.Trim(), $Step.Text
    
        if(!$StepCommand) {
            $Pester.AddTestResult($StepText, "Skipped", $null, "Could not find test for step!", $null )
        } else {
            $NamedArguments, $Parameters = Get-StepParameters $Step $StepCommand
    
            $PesterException = $null
            $watch = New-Object System.Diagnostics.Stopwatch
            $watch.Start()
            try{
                Invoke-GherkinHook BeforeStep $Step.Text $Step.Tags
    
                if($NamedArguments.Count) {
                    $ScriptBlock = { & $Script:GherkinSteps.$StepCommand @NamedArguments @Parameters }
                } else {
                    $ScriptBlock = { & $Script:GherkinSteps.$StepCommand @Parameters }
                }
                # Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $PSCmdlet.SessionState
                $null = & $ScriptBlock
    
                Invoke-GherkinHook AfterStep $Step.Text $Step.Tags
    
                $Success = "Passed"
            } catch {
                $Success = "Failed"
                $PesterException = $_
            }
    
            $watch.Stop()
    
            $Pester.AddTestResult($StepText, $Success, $watch.Elapsed, $PesterException.Exception.Message, ($PesterException.ScriptStackTrace -split "`n")[1] )
        }
    
        if(!$Quiet) {
            $Pester.testresult[-1] | Write-PesterResult
        }
    }
    
    function Get-StepParameters {
        param($Step, $CommandName)
        $Null = $Step.Text -match $CommandName
    
        $NamedArguments = @{}
        $Parameters = @{}
        foreach($kv in $Matches.GetEnumerator()) {
            switch ($kv.Name -as [int]) {
                0       {  } # toss zero (where it matches the whole string)
                $null   { $NamedArguments.($kv.Name) = $ExecutionContext.InvokeCommand.ExpandString($kv.Value)       }
                default { $Parameters.([int]$kv.Name) = $ExecutionContext.InvokeCommand.ExpandString($kv.Value) }
            }
        }
        $Parameters = @($Parameters.GetEnumerator() | Sort Name | Select -Expand Value)
    
        # TODO: Convert parsed tables to tables....
        if($Step.Argument -is [Gherkin.Ast.DataTable]) {
            $NamedArguments.Table = $Step.Argument.Rows | ConvertTo-HashTableArray
        }
        if($Step.Argument -is [Gherkin.Ast.DocString]) {
            # trim empty matches if we're attaching DocStringArgument
            $Parameters = @( $Parameters | Where { $_.Length } ) + $Step.Argument.Content
        }
    
        return @($NamedArguments, $Parameters)
    }
    
    function Convert-Tags {
        [CmdletBinding()]
        param(
            [Parameter(ValueFromPipeline=$true)]
            $InputObject,
    
            [Parameter(Position=0)]
            [string[]]$BaseTags = @()
        )
        process {
            # Adapt the Gherkin .Tags property to the way we prefer it...
            [string[]]$Tags = foreach($tag in $InputObject.Tags){ $tag.Name.TrimStart("@") }
            Add-Member -Input $InputObject -Type NoteProperty -Name Tags -Value ([string[]]($Tags + $BaseTags)) -Force
            Write-Output $InputObject
        }
    }
    
    function ConvertTo-HashTableArray {
        [CmdletBinding()]
        param(
            [Parameter(ValueFromPipeline=$true)]
            [Gherkin.Ast.TableRow[]]$InputObject
        )
        begin {
            $Names = @()
            $Table = @()
        }
        process {
            # Convert the first table row into headers:
            $Rows = @($InputObject)
            if(!$Names) {
                Write-Verbose "Reading Names from Header"
                $Header, $Rows = $Rows
                $Names = $Header.Cells | Select-Object -Expand Value
            }
    
            Write-Verbose "Processing $($Rows.Length) Rows"
            foreach($row in $Rows) {
                $result = @{}
                for($n = 0; $n -lt $Names.Length; $n++) {
                    $result.Add($Names[$n], $row.Cells[$n].Value)
                }
                $Table += @($result)
            }
        }
        end {
            Write-Output $Table
        }
    }
    
  • tools\Functions\Gherkin.Tests.ps1 Show
    if ($PSVersionTable.PSVersion.Major -le 2) { return }
    
    Set-StrictMode -Version Latest
    $scriptRoot = Split-Path (Split-Path $MyInvocation.MyCommand.Path)
    
    # Calling this in a job so we don't monkey with the active pester state that's already running
    
    $job = Start-Job -ArgumentList $scriptRoot -ScriptBlock {
        param ($scriptRoot)
        Get-Module Pester | Remove-Module -Force
        Import-Module $scriptRoot\Pester.psd1 -Force
    
        New-Object psobject -Property @{
            Results       = invoke-gherkin (Join-Path $scriptRoot Examples\Validator\Validator.feature) -PassThru -Quiet
            Mockery       = invoke-gherkin (Join-Path $scriptRoot Examples\Validator\Validator.feature) -PassThru -Tag Mockery -Quiet
            Examples      = invoke-gherkin (Join-Path $scriptRoot Examples\Validator\Validator.feature) -PassThru -Tag Examples -Quiet
            Example1      = invoke-gherkin (Join-Path $scriptRoot Examples\Validator\Validator.feature) -PassThru -Tag Example1 -Quiet
            Example2      = invoke-gherkin (Join-Path $scriptRoot Examples\Validator\Validator.feature) -PassThru -Tag Example2 -Quiet
            NamedScenario = invoke-gherkin (Join-Path $scriptRoot Examples\Validator\Validator.feature) -PassThru -ScenarioName "When something uses MyValidator" -Quiet
            NotMockery    = invoke-gherkin (Join-Path $scriptRoot Examples\Validator\Validator.feature) -PassThru -ExcludeTag Mockery -Quiet
        }
    }
    
    $gherkin = $job | Wait-Job | Receive-Job
    Remove-Job $job
    
    Describe 'Invoke-Gherkin' {
        It 'Works on the Validator example' {
            $gherkin.Results.PassedCount | Should Be $gherkin.Results.TotalCount
        }
    
        It 'Supports testing only scenarios with certain tags' {
            $gherkin.Mockery.PassedCount | Should Be $gherkin.Mockery.TotalCount
            $gherkin.Mockery.TotalCount | Should BeLessThan $gherkin.Results.TotalCount
        }
    
        It 'Supports tagging examples' {
            $gherkin.Example1.PassedCount | Should Be $gherkin.Example1.TotalCount
            $gherkin.Example1.TotalCount | Should BeLessThan $gherkin.Examples.TotalCount
    
            $gherkin.Example2.PassedCount | Should Be $gherkin.Example2.TotalCount
            $gherkin.Example2.TotalCount | Should BeLessThan $gherkin.Examples.TotalCount
    
            ($gherkin.Example1.TotalCount + $gherkin.Example2.TotalCount) | Should Be $gherkin.Examples.TotalCount
        }
    
        It 'Supports excluding scenarios by tag' {
            $gherkin.NotMockery.PassedCount | Should Be $gherkin.NotMockery.TotalCount
            $gherkin.NotMockery.TotalCount | Should BeLessThan $gherkin.Results.TotalCount
            ($gherkin.NotMockery.TotalCount + $gherkin.Mockery.TotalCount) | Should Be $gherkin.Results.TotalCount
        }
    
        It 'Supports running specific scenarios by name' {
            $gherkin.NamedScenario.PassedCount | Should Be $gherkin.Mockery.TotalCount
        }
    }
    
  • tools\Functions\GherkinHook.ps1 Show
    function Hook {
        [CmdletBinding(DefaultParameterSetName="All")]
        param(
            [Parameter(Mandatory=$True, Position=0, ParameterSetName="Tags")]
            [String[]]$Tags = @(),
    
            [Parameter(Mandatory=$True, Position=1, ParameterSetName="Tags")]
            [Parameter(Mandatory=$True, Position=0, ParameterSetName="All")]
            [ScriptBlock]$Script
        )
        $Name = $MyInvocation.InvocationName
    
        $Script:GherkinHooks.${Name} += @( @{ Tags = $Tags; Script = $Script } )
    }
    
    Set-Alias BeforeAllFeatures Hook
    Set-Alias BeforeFeature Hook
    Set-Alias BeforeScenario Hook
    Set-Alias BeforeStep Hook
    
    Set-Alias AfterAllFeatures Hook
    Set-Alias AfterFeature Hook
    Set-Alias AfterScenario Hook
    Set-Alias AfterStep Hook
    
  • tools\Functions\GherkinHook.Tests.ps1 Show
    if ($PSVersionTable.PSVersion.Major -le 2) { return }
    
    Set-StrictMode -Version Latest
    
    Describe 'Testing Gerkin Hook' {
        It 'Generates a function named "Hook" with mandatory Tags and Script parameters' {
            $command = &(Get-Module Pester) { Get-Command Hook -Module Pester }
            $command | Should Not Be $null
    
            $parameter = $command.Parameters['Tags']
            $parameter | Should Not Be $null
    
            $parameter.ParameterType.Name | Should Be 'String[]'
    
            $attribute = $parameter.Attributes | Where-Object { $_.TypeId -eq [System.Management.Automation.ParameterAttribute] }
            $isMandatory = $null -ne $attribute -and $attribute.Mandatory
    
            $isMandatory | Should Be $true
    
            $parameter = $command.Parameters['Script']
            $parameter | Should Not Be $null
    
            $parameter.ParameterType.Name | Should Be 'ScriptBlock'
    
            $attribute = $parameter.Attributes | Where-Object { $_.TypeId -eq [System.Management.Automation.ParameterAttribute] }
            $isMandatory = $null -ne $attribute -and $attribute.Mandatory
    
            $isMandatory | Should Be $true
        }
        It 'Generates aliases BeforeAllFeatures, BeforeFeature, BeforeScenario, BeforeStep, AfterAllFeatures, AfterFeature, AfterScenario, AfterStep' {
            $command = &(Get-Module Pester) { Get-Alias -Definition Hook | Select -Expand Name }
            $command | Should Be "AfterAllFeatures", "AfterFeature", "AfterScenario", "AfterStep", "BeforeAllFeatures", "BeforeFeature", "BeforeScenario", "BeforeStep"
        }
        It 'Populates the GherkinHooks module variable' {
            & ( Get-Module Pester ) {
                BeforeScenario "I Click" { }
                $GherkinHooks["BeforeScenario"].Tags
            } | Select -Last 1 | Should Be "I Click"
    
            & ( Get-Module Pester ) {
                AfterStep "I Click" { }
                $GherkinHooks["AfterStep"].Tags
            } | Select -Last 1 | Should Be "I Click"
        }
    }
    
  • tools\Functions\GherkinStep.ps1 Show
    function When {
        param(
            [Parameter(Mandatory=$True, Position=0)]
            [String]$Name,
    
            [Parameter(Mandatory=$True, Position=1)]
            [ScriptBlock]$Test
        )
    
        $Script:GherkinSteps.${Name} = $Test
    }
    
    
    Set-Alias And When
    Set-Alias But When
    Set-Alias Given When
    Set-Alias Then When
    
  • tools\Functions\GherkinStep.Tests.ps1 Show
    if ($PSVersionTable.PSVersion.Major -le 2) { return }
    
    Set-StrictMode -Version Latest
    
    Describe 'Testing Gerkin Step' {
        It 'Generates a function named "When" with mandatory name and test parameters' {
            $command = &(Get-Module Pester) { Get-Command When -Module Pester }
            $command | Should Not Be $null
    
            $parameter = $command.Parameters['Name']
            $parameter | Should Not Be $null
    
            $parameter.ParameterType.Name | Should Be 'String'
    
            $attribute = $parameter.Attributes | Where-Object { $_.TypeId -eq [System.Management.Automation.ParameterAttribute] }
            $isMandatory = $null -ne $attribute -and $attribute.Mandatory
    
            $isMandatory | Should Be $true
    
            $parameter = $command.Parameters['Test']
            $parameter | Should Not Be $null
    
            $parameter.ParameterType.Name | Should Be 'ScriptBlock'
    
            $attribute = $parameter.Attributes | Where-Object { $_.TypeId -eq [System.Management.Automation.ParameterAttribute] }
            $isMandatory = $null -ne $attribute -and $attribute.Mandatory
    
            $isMandatory | Should Be $true
        }
        It 'Generates aliases And, But, Given, Then for When' {
            $command = &(Get-Module Pester) { Get-Alias -Definition When | Select -Expand Name }
            $command | Should Be "And", "But", "Given", "Then"
        }
        It 'Populates the GherkinSteps module variable' {
            When "I Click" { }
            & ( Get-Module Pester ) { $GherkinSteps.Keys -eq "I Click" } | Should Be "I Click"
        }
    }
    
  • tools\Functions\GlobalMock-A.Tests.ps1 Show
    # This script exists to create and mock a global function, then exit.  The actual behavior
    # that we need to test is covered in GlobalMock-B.Tests.ps1, where we make sure that the
    # global function was properly restored in its scope.
    
    $functionName = '01c1a57716fe4005ac1a7bf216f38ad0'
    
    if (Test-Path Function:\$functionName)
    {
        Remove-Item Function:\$functionName -Force -ErrorAction Stop
    }
    
    function global:01c1a57716fe4005ac1a7bf216f38ad0
    {
        return 'Original Function'
    }
    
    function script:Testing
    {
        return 'Script scope'
    }
    
    Describe 'Mocking Global Functions - Part One' {
        Mock $functionName {
            return 'Mocked'
        }
    
        It 'Mocks the global function' {
            & $functionName | Should Be 'Mocked'
        }
    }
    
  • tools\Functions\GlobalMock-B.Tests.ps1 Show
    # This test depends on some state set up in GlobalMock-A.Tests.ps1.  The behavior we're verifying
    # is that global functions that have been mocked are still properly set up even after the test
    # script exits its scope.
    
    $functionName = '01c1a57716fe4005ac1a7bf216f38ad0'
    
    try
    {
        Describe 'Mocking Global Functions - Part Two' {
            It 'Restored the global function properly' {
                $globalFunctionExists = Test-Path Function:\global:$functionName
                $globalFunctionExists | Should Be $true
                & $functionName | Should Be 'Original Function'
            }
        }
    }
    finally
    {
        if (Test-Path Function:\$functionName)
        {
            Remove-Item Function:\$functionName -Force
        }
    }
    
  • tools\Functions\In.ps1 Show
    function In {
    <#
    .SYNOPSIS
    A convenience function that executes a script from a specified path.
    
    .DESCRIPTION
    Before the script block passed to the execute parameter is invoked,
    the current location is set to the path specified. Once the script
    block has been executed, the location will be reset to the location
    the script was in prior to calling In.
    
    .PARAMETER Path
    The path that the execute block will be executed in.
    
    .PARAMETER execute
    The script to be executed in the path provided.
    
    #>
    
    param(
        $path,
        [ScriptBlock] $execute
    )
        Assert-DescribeInProgress -CommandName In
    
        $old_pwd = $pwd
        & $SafeCommands['Push-Location'] $path
        $pwd = $path
        try {
            & $execute
        } finally {
            & $SafeCommands['Pop-Location']
            $pwd = $old_pwd
        }
    }
    
  • tools\Functions\In.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "the In statement" {
            Setup -Dir "test_path"
    
            It "executes a command in that directory" {
                In "$TestDrive" -Execute { "" | Out-File "test_file" }
                "$TestDrive\test_file" | Should Exist
            }
    
            It "updates the `$pwd variable when executed" {
                In "$TestDrive\test_path" -Execute { $env:Pester_Test=$pwd }
                $env:Pester_Test | Should Match "test_path"
                $env:Pester_Test=""
            }
        }
    }
    
  • tools\Functions\InModuleScope.ps1 Show
    function InModuleScope
    {
    <#
    .SYNOPSIS
       Allows you to execute parts of a test script within the
       scope of a PowerShell script module.
    .DESCRIPTION
       By injecting some test code into the scope of a PowerShell
       script module, you can use non-exported functions, aliases
       and variables inside that module, to perform unit tests on
       its internal implementation.
    
       InModuleScope may be used anywhere inside a Pester script,
       either inside or outside a Describe block.
    .PARAMETER ModuleName
       The name of the module into which the test code should be
       injected. This module must already be loaded into the current
       PowerShell session.
    .PARAMETER ScriptBlock
       The code to be executed within the script module.
    .EXAMPLE
        # The script module:
        function PublicFunction
        {
            # Does something
        }
    
        function PrivateFunction
        {
            return $true
        }
    
        Export-ModuleMember -Function PublicFunction
    
        # The test script:
    
        Import-Module MyModule
    
        InModuleScope MyModule {
            Describe 'Testing MyModule' {
                It 'Tests the Private function' {
                    PrivateFunction | Should Be $true
                }
            }
        }
    
        Normally you would not be able to access "PrivateFunction" from
        the PowerShell session, because the module only exported
        "PublicFunction".  Using InModuleScope allowed this call to
        "PrivateFunction" to work successfully.
    #>
    
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            [string]
            $ModuleName,
    
            [Parameter(Mandatory = $true)]
            [scriptblock]
            $ScriptBlock
        )
    
        if ($null -eq (& $SafeCommands['Get-Variable'] -Name Pester -ValueOnly -ErrorAction $script:IgnoreErrorPreference))
        {
            # User has executed a test script directly instead of calling Invoke-Pester
            $Pester = New-PesterState -Path (& $SafeCommands['Resolve-Path'] .) -TestNameFilter $null -TagFilter @() -ExcludeTagFilter @() -SessionState $PSCmdlet.SessionState
            $script:mockTable = @{}
        }
    
        $module = Get-ScriptModule -ModuleName $ModuleName -ErrorAction Stop
    
        $originalState = $Pester.SessionState
        $originalScriptBlockScope = Get-ScriptBlockScope -ScriptBlock $ScriptBlock
    
        try
        {
            $Pester.SessionState = $module.SessionState
    
            Set-ScriptBlockScope -ScriptBlock $ScriptBlock -SessionState $module.SessionState
    
            do
            {
                & $ScriptBlock
            } until ($true)
        }
        finally
        {
            $Pester.SessionState = $originalState
            Set-ScriptBlockScope -ScriptBlock $ScriptBlock -SessionStateInternal $originalScriptBlockScope
        }
    }
    
    function Get-ScriptModule
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            [string] $ModuleName
        )
    
        try
        {
            $modules = @(& $SafeCommands['Get-Module'] -Name $ModuleName -All -ErrorAction Stop)
        }
        catch
        {
            throw "No module named '$ModuleName' is currently loaded."
        }
    
        $scriptModules = @($modules | & $SafeCommands['Where-Object'] { $_.ModuleType -eq 'Script' })
    
        if ($scriptModules.Count -gt 1)
        {
            throw "Multiple Script modules named '$ModuleName' are currently loaded.  Make sure to remove any extra copies of the module from your session before testing."
        }
    
        if ($scriptModules.Count -eq 0)
        {
            $actualTypes = @(
                $modules |
                & $SafeCommands['Where-Object'] { $_.ModuleType -ne 'Script' } |
                & $SafeCommands['Select-Object'] -ExpandProperty ModuleType -Unique
            )
    
            $actualTypes = $actualTypes -join ', '
    
            throw "Module '$ModuleName' is not a Script module.  Detected modules of the following types: '$actualTypes'"
        }
    
        return $scriptModules[0]
    }
    
  • tools\Functions\InModuleScope.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    Describe "Module scope separation" {
        Context "When users define variables with the same name as Pester parameters" {
            $test = "This is a test."
    
            It "does not hide user variables" {
                $test | Should Be 'This is a test.'
            }
        }
    
        It "Does not expose Pester implementation details to the SUT" {
            # Changing the Get-PesterResult function's name would cause this test to pass artificially.
            # TODO : come up with a better way of verifying that only the desired commands from the Pester
            # module are visible to the SUT.
    
            (Get-Item function:\Get-PesterResult -ErrorAction SilentlyContinue) | Should Be $null
        }
    }
    
    Describe "Executing test code inside a module" {
        New-Module -Name TestModule {
            function InternalFunction { 'I am the internal function' }
            function PublicFunction   { InternalFunction }
            Export-ModuleMember -Function PublicFunction
        } | Import-Module -Force
    
        It "Cannot call module internal functions, by default" {
            { InternalFunction } | Should Throw
        }
    
        InModuleScope TestModule {
            It "Can call module internal functions using InModuleScope" {
                InternalFunction | Should Be 'I am the internal function'
            }
    
            It "Can mock functions inside the module without using Mock -ModuleName" {
                Mock InternalFunction { 'I am the mock function.' }
                InternalFunction | Should Be 'I am the mock function.'
            }
        }
    
        Remove-Module TestModule -Force
    }
    
  • tools\Functions\It.ps1 Show
    function It {
    <#
    .SYNOPSIS
    Validates the results of a test inside of a Describe block.
    
    .DESCRIPTION
    The It command is intended to be used inside of a Describe or Context Block.
    If you are familiar with the AAA pattern (Arrange-Act-Assert), the body of
    the It block is the appropriate location for an assert. The convention is to
    assert a single expectation for each It block. The code inside of the It block
    should throw a terminating error if the expectation of the test is not met and
    thus cause the test to fail. The name of the It block should expressively state
    the expectation of the test.
    
    In addition to using your own logic to test expectations and throw exceptions,
    you may also use Pester's Should command to perform assertions in plain language.
    
    .PARAMETER Name
    An expressive phrase describing the expected test outcome.
    
    .PARAMETER Test
    The script block that should throw an exception if the
    expectation of the test is not met.If you are following the
    AAA pattern (Arrange-Act-Assert), this typically holds the
    Assert.
    
    .PARAMETER Pending
    Use this parameter to explicitly mark the test as work-in-progress/not implemented/pending when you
    need to distinguish a test that fails because it is not finished yet from a tests
    that fail as a result of changes being made in the code base. An empty test, that is a
    test that contains nothing except whitespace or comments is marked as Pending by default.
    
    .PARAMETER Skip
    Use this parameter to explicitly mark the test to be skipped. This is preferable to temporarily
    commenting out a test, because the test remains listed in the output. Use the Strict parameter
    of Invoke-Pester to force all skipped tests to fail.
    
    .PARAMETER TestCases
    Optional array of hashtable (or any IDictionary) objects.  If this parameter is used,
    Pester will call the test script block once for each table in the TestCases array,
    splatting the dictionary to the test script block as input.  If you want the name of
    the test to appear differently for each test case, you can embed tokens into the Name
    parameter with the syntax 'Adds numbers <A> and <B>' (assuming you have keys named A and B
    in your TestCases hashtables.)
    
    .EXAMPLE
    function Add-Numbers($a, $b) {
        return $a + $b
    }
    
    Describe "Add-Numbers" {
        It "adds positive numbers" {
            $sum = Add-Numbers 2 3
            $sum | Should Be 5
        }
    
        It "adds negative numbers" {
            $sum = Add-Numbers (-2) (-2)
            $sum | Should Be (-4)
        }
    
        It "adds one negative number to positive number" {
            $sum = Add-Numbers (-2) 2
            $sum | Should Be 0
        }
    
        It "concatenates strings if given strings" {
            $sum = Add-Numbers two three
            $sum | Should Be "twothree"
        }
    }
    
    .EXAMPLE
    function Add-Numbers($a, $b) {
        return $a + $b
    }
    
    Describe "Add-Numbers" {
        $testCases = @(
            @{ a = 2;     b = 3;       expectedResult = 5 }
            @{ a = -2;    b = -2;      expectedResult = -4 }
            @{ a = -2;    b = 2;       expectedResult = 0 }
            @{ a = 'two'; b = 'three'; expectedResult = 'twothree' }
        )
    
        It 'Correctly adds <a> and <b> to get <expectedResult>' -TestCases $testCases {
            param ($a, $b, $expectedResult)
    
            $sum = Add-Numbers $a $b
            $sum | Should Be $expectedResult
        }
    }
    
    .LINK
    Describe
    Context
    about_should
    #>
        [CmdletBinding(DefaultParameterSetName = 'Normal')]
        param(
            [Parameter(Mandatory = $true, Position = 0)]
            [string]$name,
    
            [Parameter(Position = 1)]
            [ScriptBlock] $test = {},
    
            [System.Collections.IDictionary[]] $TestCases,
    
            [Parameter(ParameterSetName = 'Pending')]
            [Switch] $Pending,
    
            [Parameter(ParameterSetName = 'Skip')]
            [Alias('Ignore')]
            [Switch] $Skip
        )
    
        ItImpl -Pester $pester -OutputScriptBlock ${function:Write-PesterResult} @PSBoundParameters
    }
    
    function ItImpl
    {
        [CmdletBinding(DefaultParameterSetName = 'Normal')]
        param(
            [Parameter(Mandatory = $true, Position=0)]
            [string]$name,
            [Parameter(Position = 1)]
            [ScriptBlock] $test,
            [System.Collections.IDictionary[]] $TestCases,
            [Parameter(ParameterSetName = 'Pending')]
            [Switch] $Pending,
    
            [Parameter(ParameterSetName = 'Skip')]
            [Alias('Ignore')]
            [Switch] $Skip,
    
            $Pester,
            [scriptblock] $OutputScriptBlock
        )
    
        Assert-DescribeInProgress -CommandName It
    
        # Jumping through hoops to make strict mode happy.
        if ($PSCmdlet.ParameterSetName -ne 'Skip') { $Skip = $false }
        if ($PSCmdlet.ParameterSetName -ne 'Pending') { $Pending = $false }
    
        #unless Skip or Pending is specified you must specify a ScriptBlock to the Test parameter
        if (-not ($PSBoundParameters.ContainsKey('test') -or $Skip -or $Pending))
        {
            throw 'No test script block is provided. (Have you put the open curly brace on the next line?)'
        }
    
        #the function is called with Pending or Skipped set the script block if needed
        if ($null -eq $test) { $test = {} }
    
        #mark empty Its as Pending
        #[String]::IsNullOrWhitespace is not available in .NET version used with PowerShell 2
        if ($PSCmdlet.ParameterSetName -eq 'Normal' -and
           [String]::IsNullOrEmpty((Remove-Comments $test.ToString()) -replace "\s"))
        {
            $Pending = $true
        }
    
        $pendingSkip = @{}
    
        if ($PSCmdlet.ParameterSetName -eq 'Skip')
        {
            $pendingSkip['Skip'] = $Skip
        }
        else
        {
            $pendingSkip['Pending'] = $Pending
        }
    
        if ($null -ne $TestCases -and $TestCases.Count -gt 0)
        {
            foreach ($testCase in $TestCases)
            {
                $expandedName = [regex]::Replace($name, '<([^>]+)>', {
                    $capture = $args[0].Groups[1].Value
                    if ($testCase.Contains($capture))
                    {
                        $testCase[$capture]
                    }
                    else
                    {
                        "<$capture>"
                    }
                })
    
                $splat = @{
                    Name = $expandedName
                    Scriptblock = $test
                    Parameters = $testCase
                    ParameterizedSuiteName = $name
                    OutputScriptBlock = $OutputScriptBlock
                }
    
                Invoke-Test @splat @pendingSkip
            }
        }
        else
        {
            Invoke-Test -Name $name -ScriptBlock $test @pendingSkip -OutputScriptBlock $OutputScriptBlock
        }
    }
    
    function Invoke-Test
    {
        [CmdletBinding(DefaultParameterSetName = 'Normal')]
        param (
            [Parameter(Mandatory = $true)]
            [string] $Name,
    
            [Parameter(Mandatory = $true)]
            [ScriptBlock] $ScriptBlock,
    
            [scriptblock] $OutputScriptBlock,
    
            [System.Collections.IDictionary] $Parameters,
            [string] $ParameterizedSuiteName,
    
            [Parameter(ParameterSetName = 'Pending')]
            [Switch] $Pending,
    
            [Parameter(ParameterSetName = 'Skip')]
            [Alias('Ignore')]
            [Switch] $Skip
        )
    
        if ($null -eq $Parameters) { $Parameters = @{} }
    
        try
        {
            if ($Skip)
            {
                $Pester.AddTestResult($Name, "Skipped", $null)
            }
            elseif ($Pending)
            {
                $Pester.AddTestResult($Name, "Pending", $null)
            }
            else
            {
                & $SafeCommands['Write-Progress'] -Activity "Running test '$Name'" -Status Processing
    
                $errorRecord = $null
                try
                {
                    Invoke-TestCaseSetupBlocks
    
                    do
                    {
                        $null = & $ScriptBlock @Parameters
                    } until ($true)
                }
                catch
                {
                    $errorRecord = $_
                }
                finally
                {
                    #guarantee that the teardown action will run and prevent it from failing the whole suite
                    try
                    {
                        if (-not ($Skip -or $Pending))
                        {
                            Invoke-TestCaseTeardownBlocks
                        }
                    }
                    catch
                    {
                        $errorRecord = $_
                    }
                }
    
                $result = Get-PesterResult -ErrorRecord $errorRecord
                $orderedParameters = Get-OrderedParameterDictionary -ScriptBlock $ScriptBlock -Dictionary $Parameters
                $Pester.AddTestResult( $result.name, $result.Result, $null, $result.FailureMessage, $result.StackTrace, $ParameterizedSuiteName, $orderedParameters, $result.ErrorRecord )
                & $SafeCommands['Write-Progress'] -Activity "Running test '$Name'" -Completed -Status Processing
            }
        }
        finally
        {
            Exit-MockScope -ExitTestCaseOnly
        }
    
        if ($null -ne $OutputScriptBlock)
        {
            $Pester.testresult[-1] | & $OutputScriptBlock
        }
    }
    
    function Get-PesterResult {
        param(
            [Nullable[TimeSpan]] $Time,
            [System.Management.Automation.ErrorRecord] $ErrorRecord
        )
    
        $testResult = @{
            name = $name
            time = $time
            failureMessage = ""
            stackTrace = ""
            ErrorRecord = $null
            success = $false
            result = "Failed"
        };
    
        if(-not $ErrorRecord)
        {
            $testResult.Result = "Passed"
            $testResult.success = $true
            return $testResult
        }
    
        if ($ErrorRecord.FullyQualifiedErrorID -eq 'PesterAssertionFailed')
        {
            # we use TargetObject to pass structured information about the error.
            $details = $ErrorRecord.TargetObject
    
            $failureMessage = $details.Message
            $file = $details.File
            $line = $details.Line
            $lineText = "`n$line`: $($details.LineText)"
        }
        elseif ($ErrorRecord.FullyQualifiedErrorId -eq 'PesterTestInconclusive')
        {
            # we use TargetObject to pass structured information about the error.
            $details = $ErrorRecord.TargetObject
    
            $failureMessage = $details.Message
            $file = $details.File
            $line = $details.Line
            $lineText = "`n$line`: $($details.LineText)"
    
            $testResult.Result = 'Inconclusive'
        }
        else
        {
            $failureMessage = $ErrorRecord.ToString()
            $file = $ErrorRecord.InvocationInfo.ScriptName
            $line = $ErrorRecord.InvocationInfo.ScriptLineNumber
            $lineText = ''
        }
    
        $testResult.failureMessage = $failureMessage
        $testResult.stackTrace = "at line: $line in ${file}${lineText}"
        $testResult.ErrorRecord = $ErrorRecord
    
        return $testResult
    }
    
    function Remove-Comments ($Text)
    {
        $text -replace "(?s)(<#.*#>)" -replace "\#.*"
    }
    
    function Get-OrderedParameterDictionary
    {
        [OutputType([System.Collections.IDictionary])]
        param (
            [scriptblock] $ScriptBlock,
            [System.Collections.IDictionary] $Dictionary
        )
    
        $parameters = Get-ParameterDictionary -ScriptBlock $ScriptBlock
    
        $orderedDictionary = & $SafeCommands['New-Object'] System.Collections.Specialized.OrderedDictionary
    
        foreach ($parameterName in $parameters.Keys)
        {
            $value = $null
            if ($Dictionary.ContainsKey($parameterName))
            {
                $value = $Dictionary[$parameterName]
            }
    
            $orderedDictionary[$parameterName] = $value
        }
    
        return $orderedDictionary
    }
    
    function Get-ParameterDictionary
    {
        param (
            [scriptblock] $ScriptBlock
        )
    
        $guid = [guid]::NewGuid().Guid
    
        try
        {
            & $SafeCommands['Set-Content'] function:\$guid $ScriptBlock
            $metadata = [System.Management.Automation.CommandMetadata](& $SafeCommands['Get-Command'] -Name $guid -CommandType Function)
    
            return $metadata.Parameters
        }
        finally
        {
            if (& $SafeCommands['Test-Path'] function:\$guid) { & $SafeCommands['Remove-Item'] function:\$guid }
        }
    }
    
  • tools\Functions\It.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe 'Get-PesterResult' {
        }
    
        Describe 'It - Implementation' {
            $testState = New-PesterState -Path $TestDrive
    
            It 'Throws an error if you fail to pass in a test block' {
                $scriptBlock = { ItImpl -Pester $testState 'Some Name' }
                $scriptBlock | Should Throw 'No test script block is provided. (Have you put the open curly brace on the next line?)'
            }
    
            It 'Does not throw an error if It is passed a script block, and adds a successful test result.' {
                $scriptBlock = { ItImpl -Pester $testState 'Enters an It block inside a Describe' { } }
                $scriptBlock | Should Not Throw
    
                $testState.TestResult[-1].Passed | Should Be $true
                $testState.TestResult[-1].ParameterizedSuiteName | Should BeNullOrEmpty
            }
    
            It 'Does not throw an error if the -Pending switch is used, and no script block is passed' {
                $scriptBlock = { ItImpl -Pester $testState 'Some Name' -Pending }
                $scriptBlock | Should Not Throw
            }
    
            It 'Does not throw an error if the -Skip switch is used, and no script block is passed' {
                $scriptBlock = { ItImpl -Pester $testState 'Some Name' -Skip }
                $scriptBlock | Should Not Throw
            }
    
            It 'Does not throw an error if the -Ignore switch is used, and no script block is passed' {
                $scriptBlock = { ItImpl -Pester $testState 'Some Name' -Ignore }
                $scriptBlock | Should Not Throw
            }
    
            It 'Creates a pending test for an empty (whitespace and comments only) script block' {
                $scriptBlock = {
                    # Single-Line comment
                    <#
                        Multi-
                        Line-
                        Comment
                    #>
                }
    
                { ItImpl -Pester $testState 'Some Name' $scriptBlock } | Should Not Throw
                $testState.TestResult[-1].Result | Should Be 'Pending'
            }
    
            It 'Adds a failed test if the script block throws an exception' {
                $scriptBlock = {
                    ItImpl -Pester $testState 'Enters an It block inside a Describe' {
                        throw 'I am a failed test'
                    }
                }
    
                $scriptBlock | Should Not Throw
                $testState.TestResult[-1].Passed | Should Be $false
                $testState.TestResult[-1].ParameterizedSuiteName | Should BeNullOrEmpty
                $testState.TestResult[-1].FailureMessage | Should Be 'I am a failed test'
            }
    
            $script:counterNameThatIsReallyUnlikelyToConflictWithAnything = 0
    
            It 'Calls the output script block for each test' {
                $outputBlock = { $script:counterNameThatIsReallyUnlikelyToConflictWithAnything++ }
    
                ItImpl -Pester $testState 'Does something' -OutputScriptBlock $outputBlock { }
                ItImpl -Pester $testState 'Does something' -OutputScriptBlock $outputBlock { }
                ItImpl -Pester $testState 'Does something' -OutputScriptBlock $outputBlock { }
    
                $script:counterNameThatIsReallyUnlikelyToConflictWithAnything | Should Be 3
            }
    
            Remove-Variable -Scope Script -Name counterNameThatIsReallyUnlikelyToConflictWithAnything
    
            Context 'Parameterized Tests' {
                # be careful about variable naming here; with InModuleScope Pester, we can create the same types of bugs that the v3
                # scope isolation fixed for everyone else.  (Naming this variable $testCases gets hidden later by parameters of the
                # same name in It.)
    
                $cases = @(
                    @{ a = 1; b = 1; expectedResult = 2}
                    @{ a = 1; b = 2; expectedResult = 3}
                    @{ a = 5; b = 4; expectedResult = 9}
                    @{ a = 1; b = 1; expectedResult = 'Intentionally failed' }
                )
    
                $suiteName = 'Adds <a> and <b> to get <expectedResult>.  <Bogus> is not a parameter.'
    
                ItImpl -Pester $testState -Name $suiteName -TestCases $cases {
                    param ($a, $b, $expectedResult)
    
                    ($a + $b) | Should Be $expectedResult
                }
    
                It 'Creates test result records with the ParameterizedSuiteName property set' {
                    for ($i = -1; $i -ge -4; $i--)
                    {
                        $testState.TestResult[$i].ParameterizedSuiteName | Should Be $suiteName
                    }
                }
    
                It 'Expands parameters in parameterized test suite names' {
                    for ($i = -1; $i -ge -4; $i--)
                    {
                        $expectedName = "Adds $($cases[$i]['a']) and $($cases[$i]['b']) to get $($cases[$i]['expectedResult']).  <Bogus> is not a parameter."
                        $testState.TestResult[$i].Name | Should Be $expectedName
                    }
                }
    
                It 'Logs the proper successes and failures' {
                    $testState.TestResult[-1].Passed | Should Be $false
                    for ($i = -2; $i -ge -4; $i--)
                    {
                        $testState.TestResult[$i].Passed | Should Be $true
                    }
                }
            }
        }
    
        Describe 'Get-OrderedParameterDictionary' {
            $_testScriptBlock = {
                param (
                    $1, $c, $0, $z, $a, ${Something.Really/Weird }
                )
            }
    
            $hashtable = @{
                '1' = 'One'
                '0' = 'Zero'
                z = 'Z'
                a = 'A'
                c = 'C'
                'Something.Really/Weird ' = 'Weird'
            }
    
            $dictionary = Get-OrderedParameterDictionary -ScriptBlock $_testScriptBlock -Dictionary $hashtable
    
            It 'Reports keys and values in the same order as the param block' {
                ($dictionary.Keys -join ',') |
                Should Be '1,c,0,z,a,Something.Really/Weird '
    
                ($dictionary.Values -join ',') |
                Should Be 'One,C,Zero,Z,A,Weird'
            }
        }
    
        Describe 'Remove-Comments' {
            It 'Removes single line comments' {
                Remove-Comments -Text 'code #comment' | Should Be 'code '
            }
            It 'Removes multi line comments' {
                Remove-Comments -Text 'code <#comment
                comment#> code' | Should Be 'code  code'
            }
        }
    }
    
    $thisScriptRegex = [regex]::Escape($MyInvocation.MyCommand.Path)
    
    Describe 'Get-PesterResult' {
        $getPesterResult = InModuleScope Pester { ${function:Get-PesterResult} }
    
        Context 'failed tests in Tests file' {
            #the $script scriptblock below is used as a position marker to determine
            #on which line the test failed.
            $errorRecord = $null
            try{'something' | should be 'nothing'}catch{ $errorRecord=$_} ; $script={}
            $result = & $getPesterResult 0 $errorRecord
            It 'records the correct stack line number' {
                $result.Stacktrace | should match "at line: $($script.startPosition.StartLine) in $thisScriptRegex"
            }
            It 'records the correct error record' {
                $result.ErrorRecord -is [System.Management.Automation.ErrorRecord] | Should be $true
                $result.ErrorRecord.Exception.Message | Should match 'Expected: {nothing}'
            }
        }
        It 'Does not modify the error message from the original exception' {
            $object = New-Object psobject
            $message = 'I am an error.'
            Add-Member -InputObject $object -MemberType ScriptMethod -Name ThrowSomething -Value { throw $message }
    
            $errorRecord = $null
            try { $object.ThrowSomething() } catch { $errorRecord = $_ }
    
            $pesterResult = & $getPesterResult 0 $errorRecord
    
            $pesterResult.FailureMessage | Should Be $errorRecord.Exception.Message
        }
        Context 'failed tests in another file' {
            $errorRecord = $null
    
            $testPath = Join-Path $TestDrive test.ps1
            $escapedTestPath = [regex]::Escape($testPath)
    
            Set-Content -Path $testPath -Value "`r`n'One' | Should Be 'Two'"
    
            try
            {
                & $testPath
            }
            catch
            {
                $errorRecord = $_
            }
    
            $result = & $getPesterResult 0 $errorRecord
    
    
            It 'records the correct stack line number' {
                $result.Stacktrace | should match "at line: 2 in $escapedTestPath"
            }
            It 'records the correct error record' {
                $result.ErrorRecord -is [System.Management.Automation.ErrorRecord] | Should be $true
                $result.ErrorRecord.Exception.Message | Should match 'Expected: {Two}'
            }
        }
    }
    
  • tools\Functions\Mock.ps1 Show
    function Mock {
    
    <#
    .SYNOPSIS
    Mocks the behavior of an existing command with an alternate
    implementation.
    
    .DESCRIPTION
    This creates new behavior for any existing command within the scope of a
    Describe or Context block. The function allows you to specify a script block
    that will become the command's new behavior.
    
    Optionally, you may create a Parameter Filter which will examine the
    parameters passed to the mocked command and will invoke the mocked
    behavior only if the values of the parameter values pass the filter. If
    they do not, the original command implementation will be invoked instead
    of a mock.
    
    You may create multiple mocks for the same command, each using a different
    ParameterFilter. ParameterFilters will be evaluated in reverse order of
    their creation. The last one created will be the first to be evaluated.
    The mock of the first filter to pass will be used. The exception to this
    rule are Mocks with no filters. They will always be evaluated last since
    they will act as a "catch all" mock.
    
    Mocks can be marked Verifiable. If so, the Assert-VerifiableMocks command
    can be used to check if all Verifiable mocks were actually called. If any
    verifiable mock is not called, Assert-VerifiableMocks will throw an
    exception and indicate all mocks not called.
    
    If you wish to mock commands that are called from inside a script module,
    you can do so by using the -ModuleName parameter to the Mock command. This
    injects the mock into the specified module. If you do not specify a
    module name, the mock will be created in the same scope as the test script.
    You may mock the same command multiple times, in different scopes, as needed.
    Each module's mock maintains a separate call history and verified status.
    
    .PARAMETER CommandName
    The name of the command to be mocked.
    
    .PARAMETER MockWith
    A ScriptBlock specifying the behavior that will be used to mock CommandName.
    The default is an empty ScriptBlock.
    NOTE: Do not specify param or dynamicparam blocks in this script block.
    These will be injected automatically based on the signature of the command
    being mocked, and the MockWith script block can contain references to the
    mocked commands parameter variables.
    
    .PARAMETER Verifiable
    When this is set, the mock will be checked when Assert-VerifiableMocks is
    called.
    
    .PARAMETER ParameterFilter
    An optional filter to limit mocking behavior only to usages of
    CommandName where the values of the parameters passed to the command
    pass the filter.
    
    This ScriptBlock must return a boolean value. See examples for usage.
    
    .PARAMETER ModuleName
    Optional string specifying the name of the module where this command
    is to be mocked.  This should be a module that _calls_ the mocked
    command; it doesn't necessarily have to be the same module which
    originally implemented the command.
    
    .EXAMPLE
    Mock Get-ChildItem { return @{FullName = "A_File.TXT"} }
    
    Using this Mock, all calls to Get-ChildItem will return a hashtable with a
    FullName property returning "A_File.TXT"
    
    .EXAMPLE
    Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp) }
    
    This Mock will only be applied to Get-ChildItem calls within the user's temp directory.
    
    .EXAMPLE
    Mock Set-Content {} -Verifiable -ParameterFilter { $Path -eq "some_path" -and $Value -eq "Expected Value" }
    
    When this mock is used, if the Mock is never invoked and Assert-VerifiableMocks is called, an exception will be thrown. The command behavior will do nothing since the ScriptBlock is empty.
    
    .EXAMPLE
    Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp\1) }
    Mock Get-ChildItem { return @{FullName = "B_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp\2) }
    Mock Get-ChildItem { return @{FullName = "C_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp\3) }
    
    Multiple mocks of the same command may be used. The parameter filter determines which is invoked. Here, if Get-ChildItem is called on the "2" directory of the temp folder, then B_File.txt will be returned.
    
    .EXAMPLE
    Mock Get-ChildItem { return @{FullName="B_File.TXT"} } -ParameterFilter { $Path -eq "$env:temp\me" }
    Mock Get-ChildItem { return @{FullName="A_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp) }
    
    Get-ChildItem $env:temp\me
    
    Here, both mocks could apply since both filters will pass. A_File.TXT will be returned because it was the most recent Mock created.
    
    .EXAMPLE
    Mock Get-ChildItem { return @{FullName = "B_File.TXT"} } -ParameterFilter { $Path -eq "$env:temp\me" }
    Mock Get-ChildItem { return @{FullName = "A_File.TXT"} }
    
    Get-ChildItem c:\windows
    
    Here, A_File.TXT will be returned. Since no filter was specified, it will apply to any call to Get-ChildItem that does not pass another filter.
    
    .EXAMPLE
    Mock Get-ChildItem { return @{FullName = "B_File.TXT"} } -ParameterFilter { $Path -eq "$env:temp\me" }
    Mock Get-ChildItem { return @{FullName = "A_File.TXT"} }
    
    Get-ChildItem $env:temp\me
    
    Here, B_File.TXT will be returned. Even though the filterless mock was created more recently. This illustrates that filterless Mocks are always evaluated last regardless of their creation order.
    
    .EXAMPLE
    Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } -ModuleName MyTestModule
    
    Using this Mock, all calls to Get-ChildItem from within the MyTestModule module
    will return a hashtable with a FullName property returning "A_File.TXT"
    
    .EXAMPLE
    Get-Module -Name ModuleMockExample | Remove-Module
    New-Module -Name ModuleMockExample  -ScriptBlock {
        function Hidden { "Internal Module Function" }
        function Exported { Hidden }
    
        Export-ModuleMember -Function Exported
    } | Import-Module -Force
    
    Describe "ModuleMockExample" {
    
        It "Hidden function is not directly accessible outside the module" {
            { Hidden } | Should Throw
        }
    
        It "Original Hidden function is called" {
            Exported | Should Be "Internal Module Function"
        }
    
        It "Hidden is replaced with our implementation" {
            Mock Hidden { "Mocked" } -ModuleName ModuleMockExample
            Exported | Should Be "Mocked"
        }
    }
    
    This example shows how calls to commands made from inside a module can be
    mocked by using the -ModuleName parameter.
    
    
    .LINK
    Assert-MockCalled
    Assert-VerifiableMocks
    Describe
    Context
    It
    about_Should
    about_Mocking
    #>
    
        param(
            [string]$CommandName,
            [ScriptBlock]$MockWith={},
            [switch]$Verifiable,
            [ScriptBlock]$ParameterFilter = {$True},
            [string]$ModuleName
        )
    
        Assert-DescribeInProgress -CommandName Mock
    
        $contextInfo = Validate-Command $CommandName $ModuleName
        $CommandName = $contextInfo.Command.Name
    
        if ($contextInfo.Session.Module -and $contextInfo.Session.Module.Name)
        {
            $ModuleName = $contextInfo.Session.Module.Name
        }
        else
        {
            $ModuleName = ''
        }
    
        if (Test-IsClosure -ScriptBlock $MockWith)
        {
            # If the user went out of their way to call GetNewClosure(), go ahead and leave the block bound to that
            # dynamic module's scope.
            $mockWithCopy = $MockWith
        }
        else
        {
            $mockWithCopy = [scriptblock]::Create($MockWith.ToString())
            Set-ScriptBlockScope -ScriptBlock $mockWithCopy -SessionState $contextInfo.Session
        }
    
        $block = @{
            Mock       = $mockWithCopy
            Filter     = $ParameterFilter
            Verifiable = $Verifiable
            Scope      = $pester.CurrentTestGroup
        }
    
        $mock = $mockTable["$ModuleName||$CommandName"]
    
        if (-not $mock)
        {
            $metadata                = $null
            $cmdletBinding           = ''
            $paramBlock              = ''
            $dynamicParamBlock       = ''
            $dynamicParamScriptBlock = $null
    
            if ($contextInfo.Command.psobject.Properties['ScriptBlock'] -or $contextInfo.Command.CommandType -eq 'Cmdlet')
            {
                $metadata = [System.Management.Automation.CommandMetaData]$contextInfo.Command
                $null = $metadata.Parameters.Remove('Verbose')
                $null = $metadata.Parameters.Remove('Debug')
                $null = $metadata.Parameters.Remove('ErrorAction')
                $null = $metadata.Parameters.Remove('WarningAction')
                $null = $metadata.Parameters.Remove('ErrorVariable')
                $null = $metadata.Parameters.Remove('WarningVariable')
                $null = $metadata.Parameters.Remove('OutVariable')
                $null = $metadata.Parameters.Remove('OutBuffer')
    
                # Some versions of PowerShell may include dynamic parameters here
                # We will filter them out and add them at the end to be
                # compatible with both earlier and later versions
                $dynamicParams = $metadata.Parameters.Values | & $SafeCommands['Where-Object'] {$_.IsDynamic}
                if($dynamicParams -ne $null) {
                    $dynamicparams | & $SafeCommands['ForEach-Object'] { $null = $metadata.Parameters.Remove($_.name) }
                }
    
                $cmdletBinding = [Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($metadata)
                if ($global:PSVersionTable.PSVersion.Major -ge 3 -and $contextInfo.Command.CommandType -eq 'Cmdlet') {
                    if ($cmdletBinding -ne '[CmdletBinding()]') {
                        $cmdletBinding = $cmdletBinding.Insert($cmdletBinding.Length-2, ',')
                    }
                    $cmdletBinding = $cmdletBinding.Insert($cmdletBinding.Length-2, 'PositionalBinding=$false')
                }
    
                $paramBlock    = [Management.Automation.ProxyCommand]::GetParamBlock($metadata)
    
                if ($contextInfo.Command.CommandType -eq 'Cmdlet')
                {
                    $dynamicParamBlock = "dynamicparam { Get-MockDynamicParameters -CmdletName '$($contextInfo.Command.Name)' -Parameters `$PSBoundParameters }"
                }
                else
                {
                    $dynamicParamStatements = Get-DynamicParamBlock -ScriptBlock $contextInfo.Command.ScriptBlock
    
                    if ($dynamicParamStatements -match '\S')
                    {
                        $metadataSafeForDynamicParams = [System.Management.Automation.CommandMetaData]$contextInfo.Command
                        foreach ($param in $metadataSafeForDynamicParams.Parameters.Values)
                        {
                            $param.ParameterSets.Clear()
                        }
    
                        $paramBlockSafeForDynamicParams = [System.Management.Automation.ProxyCommand]::GetParamBlock($metadataSafeForDynamicParams)
                        $comma = if ($metadataSafeForDynamicParams.Parameters.Count -gt 0) { ',' } else { '' }
                        $dynamicParamBlock = "dynamicparam { Get-MockDynamicParameters -ModuleName '$ModuleName' -FunctionName '$CommandName' -Parameters `$PSBoundParameters -Cmdlet `$PSCmdlet }"
    
                        $code = @"
                            $cmdletBinding
                            param(
                                [object] `${P S Cmdlet}$comma
                                $paramBlockSafeForDynamicParams
                            )
    
                            `$PSCmdlet = `${P S Cmdlet}
    
                            $dynamicParamStatements
    "@
    
                        $dynamicParamScriptBlock = [scriptblock]::Create($code)
    
                        $sessionStateInternal = Get-ScriptBlockScope -ScriptBlock $contextInfo.Command.ScriptBlock
    
                        if ($null -ne $sessionStateInternal)
                        {
                            Set-ScriptBlockScope -ScriptBlock $dynamicParamScriptBlock -SessionStateInternal $sessionStateInternal
                        }
                    }
                }
            }
    
            $EscapeSingleQuotedStringContent =
                if ($global:PSVersionTable.PSVersion.Major -ge 5) {
                    { [System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($args[0]) }
                } else {
                    { $args[0] -replace "['‘’‚‛]", '$&$&' }
                }
    
            $newContent = & $SafeCommands['Get-Content'] function:\MockPrototype
            $newContent = $newContent -replace '#FUNCTIONNAME#', (& $EscapeSingleQuotedStringContent $CommandName)
            $newContent = $newContent -replace '#MODULENAME#', (& $EscapeSingleQuotedStringContent $ModuleName)
    
            $canCaptureArgs = 'true'
            if ($contextInfo.Command.CommandType -eq 'Cmdlet' -or
                ($contextInfo.Command.CommandType -eq 'Function' -and $contextInfo.Command.CmdletBinding)) {
                $canCaptureArgs = 'false'
            }
            $newContent = $newContent -replace '#CANCAPTUREARGS#', $canCaptureArgs
    
            $code = @"
                $cmdletBinding
                param ( $paramBlock )
                $dynamicParamBlock
                begin
                {
                    `${mock call state} = @{}
                    $($newContent -replace '#BLOCK#', 'Begin' -replace '#INPUT#')
                }
    
                process
                {
                    $($newContent -replace '#BLOCK#', 'Process' -replace '#INPUT#', '-InputObject @($input)')
                }
    
                end
                {
                    $($newContent -replace '#BLOCK#', 'End' -replace '#INPUT#')
                }
    "@
    
            $mockScript = [scriptblock]::Create($code)
    
            $mock = @{
                OriginalCommand         = $contextInfo.Command
                Blocks                  = @()
                CommandName             = $CommandName
                SessionState            = $contextInfo.Session
                Scope                   = $pester.CurrentTestGroup
                PesterState             = $pester
                Metadata                = $metadata
                CallHistory             = @()
                DynamicParamScriptBlock = $dynamicParamScriptBlock
                FunctionScope           = ''
                Alias                   = $null
            }
    
            $mockTable["$ModuleName||$CommandName"] = $mock
    
            if ($contextInfo.Command.CommandType -eq 'Function')
            {
                $mock['FunctionScope'] = $contextInfo.Scope
    
                $scriptBlock =
                {
                    param ( [string] $CommandName )
    
                    if ($ExecutionContext.InvokeProvider.Item.Exists("Function:\$CommandName", $true, $true))
                    {
                        $ExecutionContext.InvokeProvider.Item.Rename([System.Management.Automation.WildcardPattern]::Escape("Function:\$CommandName"), "script:PesterIsMocking_$CommandName", $true)
                    }
                }
    
                $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $scriptBlock -ArgumentList $CommandName
            }
    
            $scriptBlock = { $ExecutionContext.InvokeProvider.Item.Set("Function:\script:$($args[0])", $args[1], $true, $true) }
            $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $scriptBlock -ArgumentList $CommandName, $mockScript
    
            if ($mock.OriginalCommand.ModuleName)
            {
                $mock.Alias = "$($mock.OriginalCommand.ModuleName)\$($CommandName)"
    
                $scriptBlock = {
                    $setAlias = & (Pester\SafeGetCommand) -Name Set-Alias -CommandType Cmdlet -Module Microsoft.PowerShell.Utility
                    & $setAlias -Name $args[0] -Value $args[1] -Scope Script
                }
    
                $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $scriptBlock -ArgumentList $mock.Alias, $CommandName
            }
        }
    
        $mock.Blocks = @(
            $mock.Blocks | & $SafeCommands['Where-Object'] { $_.Filter.ToString() -eq '$True' }
            if ($block.Filter.ToString() -eq '$True') { $block }
    
            $mock.Blocks | & $SafeCommands['Where-Object'] { $_.Filter.ToString() -ne '$True' }
            if ($block.Filter.ToString() -ne '$True') { $block }
        )
    }
    
    
    function Assert-VerifiableMocks {
    <#
    .SYNOPSIS
    Checks if any Verifiable Mock has not been invoked. If so, this will throw an exception.
    
    .DESCRIPTION
    This can be used in tandem with the -Verifiable switch of the Mock
    function. Mock can be used to mock the behavior of an existing command
    and optionally take a -Verifiable switch. When Assert-VerifiableMocks
    is called, it checks to see if any Mock marked Verifiable has not been
    invoked. If any mocks have been found that specified -Verifiable and
    have not been invoked, an exception will be thrown.
    
    .EXAMPLE
    Mock Set-Content {} -Verifiable -ParameterFilter {$Path -eq "some_path" -and $Value -eq "Expected Value"}
    
    { ...some code that never calls Set-Content some_path -Value "Expected Value"... }
    
    Assert-VerifiableMocks
    
    This will throw an exception and cause the test to fail.
    
    .EXAMPLE
    Mock Set-Content {} -Verifiable -ParameterFilter {$Path -eq "some_path" -and $Value -eq "Expected Value"}
    
    Set-Content some_path -Value "Expected Value"
    
    Assert-VerifiableMocks
    
    This will not throw an exception because the mock was invoked.
    
    #>
        Assert-DescribeInProgress -CommandName Assert-VerifiableMocks
    
        [email protected]{}
        $mockTable.Keys | & $SafeCommands['ForEach-Object'] {
            $m=$_;
    
            $mockTable[$m].blocks |
            & $SafeCommands['Where-Object'] { $_.Verifiable } |
            & $SafeCommands['ForEach-Object'] { $unVerified[$m]=$_ }
        }
        if($unVerified.Count -gt 0) {
            foreach($mock in $unVerified.Keys){
                $array = $mock -split '\|\|'
                $function = $array[1]
                $module = $array[0]
    
                $message = "`r`n Expected $function "
                if ($module) { $message += "in module $module " }
                $message += "to be called with $($unVerified[$mock].Filter)"
            }
            throw $message
        }
    }
    
    function Assert-MockCalled {
    <#
    .SYNOPSIS
    Checks if a Mocked command has been called a certain number of times
    and throws an exception if it has not.
    
    .DESCRIPTION
    This command verifies that a mocked command has been called a certain number
    of times.  If the call history of the mocked command does not match the parameters
    passed to Assert-MockCalled, Assert-MockCalled will throw an exception.
    
    .PARAMETER CommandName
    The mocked command whose call history should be checked.
    
    .PARAMETER ModuleName
    The module where the mock being checked was injected.  This is optional,
    and must match the ModuleName that was used when setting up the Mock.
    
    .PARAMETER Times
    The number of times that the mock must be called to avoid an exception
    from throwing.
    
    .PARAMETER Exactly
    If this switch is present, the number specified in Times must match
    exactly the number of times the mock has been called. Otherwise it
    must match "at least" the number of times specified.  If the value
    passed to the Times parameter is zero, the Exactly switch is implied.
    
    .PARAMETER ParameterFilter
    An optional filter to qualify wich calls should be counted. Only those
    calls to the mock whose parameters cause this filter to return true
    will be counted.
    
    .PARAMETER ExclusiveFilter
    Like ParameterFilter, except when you use ExclusiveFilter, and there
    were any calls to the mocked command which do not match the filter,
    an exception will be thrown.  This is a convenient way to avoid needing
    to have two calls to Assert-MockCalled like this:
    
    Assert-MockCalled SomeCommand -Times 1 -ParameterFilter { $something -eq $true }
    Assert-MockCalled SomeCommand -Times 0 -ParameterFilter { $something -ne $true }
    
    .PARAMETER Scope
    An optional parameter specifying the Pester scope in which to check for
    calls to the mocked command.  By default, Assert-MockCalled will find
    all calls to the mocked command in the current Context block (if present),
    or the current Describe block (if there is no active Context.)  Valid
    values are Describe, Context and It. If you use a scope of Describe or
    Context, the command will identify all calls to the mocked command in the
    current Describe / Context block, as well as all child scopes of that block.
    
    .EXAMPLE
    C:\PS>Mock Set-Content {}
    
    {... Some Code ...}
    
    C:\PS>Assert-MockCalled Set-Content
    
    This will throw an exception and cause the test to fail if Set-Content is not called in Some Code.
    
    .EXAMPLE
    C:\PS>Mock Set-Content -parameterFilter {$path.StartsWith("$env:temp\")}
    
    {... Some Code ...}
    
    C:\PS>Assert-MockCalled Set-Content 2 { $path -eq "$env:temp\test.txt" }
    
    This will throw an exception if some code calls Set-Content on $path=$env:temp\test.txt less than 2 times
    
    .EXAMPLE
    C:\PS>Mock Set-Content {}
    
    {... Some Code ...}
    
    C:\PS>Assert-MockCalled Set-Content 0
    
    This will throw an exception if some code calls Set-Content at all
    
    .EXAMPLE
    C:\PS>Mock Set-Content {}
    
    {... Some Code ...}
    
    C:\PS>Assert-MockCalled Set-Content -Exactly 2
    
    This will throw an exception if some code does not call Set-Content Exactly two times.
    
    .EXAMPLE
    Describe 'Assert-MockCalled Scope behavior' {
        Mock Set-Content { }
    
        It 'Calls Set-Content at least once in the It block' {
            {... Some Code ...}
    
            Assert-MockCalled Set-Content -Exactly 0 -Scope It
        }
    }
    
    Checks for calls only within the current It block.
    
    .EXAMPLE
    Describe 'Describe' {
        Mock -ModuleName SomeModule Set-Content { }
    
        {... Some Code ...}
    
        It 'Calls Set-Content at least once in the Describe block' {
            Assert-MockCalled -ModuleName SomeModule Set-Content
        }
    }
    
    Checks for calls to the mock within the SomeModule module.  Note that both the Mock
    and Assert-MockCalled commands use the same module name.
    
    .EXAMPLE
    Assert-MockCalled Get-ChildItem -ExclusiveFilter { $Path -eq 'C:\' }
    
    Checks to make sure that Get-ChildItem was called at least one time with
    the -Path parameter set to 'C:\', and that it was not called at all with
    the -Path parameter set to any other value.
    
    .NOTES
    The parameter filter passed to Assert-MockCalled does not necessarily have to match the parameter filter
    (if any) which was used to create the Mock.  Assert-MockCalled will find any entry in the command history
    which matches its parameter filter, regardless of how the Mock was created.  However, if any calls to the
    mocked command are made which did not match any mock's parameter filter (resulting in the original command
    being executed instead of a mock), these calls to the original command are not tracked in the call history.
    In other words, Assert-MockCalled can only be used to check for calls to the mocked implementation, not
    to the original.
    
    #>
    
    [CmdletBinding(DefaultParameterSetName = 'ParameterFilter')]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$CommandName,
    
        [Parameter(Position = 1)]
        [int]$Times=1,
    
        [Parameter(ParameterSetName = 'ParameterFilter', Position = 2)]
        [ScriptBlock]$ParameterFilter = {$True},
    
        [Parameter(ParameterSetName = 'ExclusiveFilter', Mandatory = $true)]
        [scriptblock] $ExclusiveFilter,
    
        [Parameter(Position = 3)]
        [string] $ModuleName,
    
        [Parameter(Position = 4)]
        [ValidateScript({
            if ([uint32]::TryParse($_, [ref] $null) -or
                $_ -eq 'Describe' -or
                $_ -eq 'Context' -or
                $_ -eq 'It')
            {
                return $true
            }
    
            throw "Scope argument must either be an unsigned integer, or one of the words 'Describe', 'Scope', or 'It'."
        })]
        [string] $Scope,
    
        [switch]$Exactly
    )
    
        if ($PSCmdlet.ParameterSetName -eq 'ParameterFilter')
        {
            $filter = $ParameterFilter
            $filterIsExclusive = $false
        }
        else
        {
            $filter = $ExclusiveFilter
            $filterIsExclusive = $true
        }
    
        Assert-DescribeInProgress -CommandName Assert-MockCalled
    
        if (-not $PSBoundParameters.ContainsKey('ModuleName') -and $null -ne $pester.SessionState.Module)
        {
            $ModuleName = $pester.SessionState.Module.Name
        }
    
        $contextInfo = Validate-Command $CommandName $ModuleName
        $CommandName = $contextInfo.Command.Name
    
        $mock = $script:mockTable["$ModuleName||$CommandName"]
    
        $moduleMessage = ''
        if ($ModuleName)
        {
            $moduleMessage = " in module $ModuleName"
        }
    
        if (-not $mock)
        {
            throw "You did not declare a mock of the $commandName Command${moduleMessage}."
        }
    
        if (-not $PSBoundParameters.ContainsKey('Scope'))
        {
            $scope = 1
        }
    
        $matchingCalls = & $SafeCommands['New-Object'] System.Collections.ArrayList
        $nonMatchingCalls = & $SafeCommands['New-Object'] System.Collections.ArrayList
    
        foreach ($historyEntry in $mock.CallHistory)
        {
            if (-not (Test-MockCallScope -CallScope $historyEntry.Scope -DesiredScope $Scope)) { continue }
    
            $params = @{
                ScriptBlock     = $filter
                BoundParameters = $historyEntry.BoundParams
                ArgumentList    = $historyEntry.Args
                Metadata        = $mock.Metadata
            }
    
    
            if (Test-ParameterFilter @params)
            {
                $null = $matchingCalls.Add($historyEntry)
            }
            else
            {
                $null = $nonMatchingCalls.Add($historyEntry)
            }
        }
    
        $lineText = $MyInvocation.Line.TrimEnd("`n")
        $line = $MyInvocation.ScriptLineNumber
    
        if($matchingCalls.Count -ne $times -and ($Exactly -or ($times -eq 0)))
        {
            $failureMessage = "Expected ${commandName}${moduleMessage} to be called $times times exactly but was called $($matchingCalls.Count) times"
            throw ( New-ShouldErrorRecord -Message $failureMessage -Line $line -LineText $lineText)
        }
        elseif($matchingCalls.Count -lt $times)
        {
            $failureMessage = "Expected ${commandName}${moduleMessage} to be called at least $times times but was called $($matchingCalls.Count) times"
            throw ( New-ShouldErrorRecord -Message $failureMessage -Line $line -LineText $lineText)
        }
        elseif ($filterIsExclusive -and $nonMatchingCalls.Count -gt 0)
        {
            $failureMessage = "Expected ${commandName}${moduleMessage} to only be called with with parameters matching the specified filter, but $($nonMatchingCalls.Count) non-matching calls were made"
            throw ( New-ShouldErrorRecord -Message $failureMessage -Line $line -LineText $lineText)
        }
    }
    
    function Test-MockCallScope
    {
        [CmdletBinding()]
        param (
            [object] $CallScope,
            [string] $DesiredScope
        )
    
        if ($null -eq $CallScope)
        {
            # This indicates a call from the current test case ("It" block), which always passes Test-MockCallScope
            return $true
        }
    
        $testGroups = $pester.TestGroups
        [Array]::Reverse($testGroups)
    
        $target = 0
        $isNumberedScope = [int]::TryParse($DesiredScope, [ref] $target)
    
        # The Describe / Context stuff here is for backward compatibility.  May be deprecated / removed in the future.
        $actualScopeNumber = -1
        $describe = -1
        $context = -1
    
        for ($i = 0; $i -lt $testGroups.Count; $i++)
        {
            if ($CallScope -eq $testGroups[$i])
            {
                $actualScopeNumber = $i
                if ($isNumberedScope) { break }
            }
    
            if ($describe -lt 0 -and $testGroups[$i].Hint -eq 'Describe') { $describe = $i }
            if ($context -lt 0 -and $testGroups[$i].Hint -eq 'Context') { $context = $i }
        }
    
        if ($actualScopeNumber -lt 0)
        {
            # this should never happen; if we get here, it's a Pester bug.
    
            throw "Pester error: Corrupted mock call history table."
        }
    
        if ($isNumberedScope)
        {
            # For this, we consider scope 0 to be the current test case / It block, scope 1 to be the first Test Group up the stack, etc.
            # $actualScopeNumber currently off by one from that scale (zero-indexed for test groups only; we already checked for the 0 case
            # farther up, which only applies if $CallScope is $null).
            return $target -gt $actualScopeNumber
        }
        else
        {
            if ($DesiredScope -eq 'Describe') { return $describe -ge $actualScopeNumber }
            if ($DesiredScope -eq 'Context')  { return $context -ge $actualScopeNumber }
        }
    
        return $false
    }
    
    function Exit-MockScope {
        param (
            [switch] $ExitTestCaseOnly
        )
    
        if ($null -eq $mockTable) { return }
    
        $removeMockStub =
        {
            param (
                [string] $CommandName,
                [string] $Scope,
                [string] $Alias
            )
    
            $ExecutionContext.InvokeProvider.Item.Remove("Function:\$CommandName", $false, $true, $true)
            if ($ExecutionContext.InvokeProvider.Item.Exists("Function:\PesterIsMocking_$CommandName", $true, $true))
            {
                $ExecutionContext.InvokeProvider.Item.Rename([System.Management.Automation.WildcardPattern]::Escape("Function:\PesterIsMocking_$CommandName"), "$Scope$CommandName", $true)
            }
    
            if ($Alias -and $ExecutionContext.InvokeProvider.Item.Exists("Alias:$Alias", $true, $true))
            {
                $ExecutionContext.InvokeProvider.Item.Remove("Alias:$Alias", $false, $true, $true)
            }
        }
    
        $mockKeys = [string[]]$mockTable.Keys
    
        foreach ($mockKey in $mockKeys)
        {
            $mock = $mockTable[$mockKey]
    
            $shouldRemoveMock = (-not $ExitTestCaseOnly) -and (ShouldRemoveMock -Mock $mock -ActivePesterState $pester)
            if ($shouldRemoveMock)
            {
                $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $removeMockStub -ArgumentList $mock.CommandName, $mock.FunctionScope, $mock.Alias
                $mockTable.Remove($mockKey)
            }
            elseif ($mock.PesterState -eq $pester)
            {
                if (-not $ExitTestCaseOnly)
                {
                    $mock.Blocks = @($mock.Blocks | & $SafeCommands['Where-Object'] { $_.Scope -ne $pester.CurrentTestGroup })
                }
    
                $testGroups = @($pester.TestGroups)
    
                $parentTestGroup = $null
    
                if ($testGroups.Count -gt 1)
                {
                    $parentTestGroup = $testGroups[-2]
                }
    
                foreach ($historyEntry in $mock.CallHistory)
                {
                    if ($ExitTestCaseOnly)
                    {
                        if ($historyEntry.Scope -eq $null) { $historyEntry.Scope = $pester.CurrentTestGroup }
                    }
                    elseif ($parentTestGroup)
                    {
                        if ($historyEntry.Scope -eq $pester.CurrentTestGroup) { $historyEntry.Scope = $parentTestGroup }
                    }
                }
            }
        }
    }
    
    function ShouldRemoveMock($Mock, $ActivePesterState)
    {
        if ($ActivePesterState -ne $mock.PesterState) { return $false }
        if ($mock.Scope -eq $ActivePesterState.CurrentTestGroup) { return $true }
    
        # These two should conditions should _probably_ never happen, because the above condition should
        # catch it, but just in case:
        if ($ActivePesterState.TestGroups.Count -eq 1) { return $true }
        if ($ActivePesterState.TestGroups[-2].Hint -eq 'Root') { return $true }
    
        return $false
    }
    
    function Validate-Command([string]$CommandName, [string]$ModuleName) {
        $module = $null
        $origCommand = $null
    
        $commandInfo = & $SafeCommands['New-Object'] psobject -Property @{ Command = $null; Scope = '' }
    
        $scriptBlock = {
            $getContentCommand = & (Pester\SafeGetCommand) Get-Content -Module Microsoft.PowerShell.Management -CommandType Cmdlet
            $newObjectCommand  = & (Pester\SafeGetCommand) New-Object  -Module Microsoft.PowerShell.Utility    -CommandType Cmdlet
    
            $command = $ExecutionContext.InvokeCommand.GetCommand($args[0], 'All')
            while ($null -ne $command -and $command.CommandType -eq [System.Management.Automation.CommandTypes]::Alias)
            {
                $command = $command.ResolvedCommand
            }
    
            $properties = @{
                Command = $command
            }
    
            if ($null -ne $command -and $command.CommandType -eq 'Function')
            {
                if ($ExecutionContext.InvokeProvider.Item.Exists("function:\global:$($command.Name)", $true, $true) -and
                    (& $getContentCommand -LiteralPath "function:\global:$($command.Name)" -ErrorAction Stop) -eq $command.ScriptBlock)
                {
                    $properties['Scope'] = 'global:'
                }
                elseif ($ExecutionContext.InvokeProvider.Item.Exists("function:\script:$($command.Name)", $true, $true) -and
                        (& $getContentCommand -LiteralPath "function:\script:$($command.Name)" -ErrorAction Stop) -eq $command.ScriptBlock)
                {
                    $properties['Scope'] = 'script:'
                }
                else
                {
                    $properties['Scope'] = ''
                }
            }
    
            return & $newObjectCommand psobject -Property $properties
        }
    
        if ($ModuleName) {
            $module = Get-ScriptModule -ModuleName $ModuleName -ErrorAction Stop
            $commandInfo = & $module $scriptBlock $CommandName
        }
    
        $session = $pester.SessionState
    
        if (-not $commandInfo.Command) {
            Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $session
            $commandInfo = & $scriptBlock $commandName
        }
    
        if (-not $commandInfo.Command) {
            throw ([System.Management.Automation.CommandNotFoundException] "Could not find Command $commandName")
        }
    
        if ($module) {
            $session = & $module { $ExecutionContext.SessionState }
        }
    
        $hash = @{Command = $commandInfo.Command; Session = $session}
        if ($commandInfo.Command.CommandType -eq 'Function')
        {
            $hash['Scope'] = $commandInfo.Scope
        }
    
        return $hash
    }
    
    function MockPrototype {
        if ($PSVersionTable.PSVersion.Major -ge 3)
        {
            [string] ${ignore preference} = 'Ignore'
        }
        else
        {
            [string] ${ignore preference} = 'SilentlyContinue'
        }
    
        ${get Variable Command} = & (Pester\SafeGetCommand) -Name Get-Variable -Module Microsoft.PowerShell.Utility -CommandType Cmdlet
    
        [object] ${a r g s} = $null
        if (${#CANCAPTUREARGS#}) {
            ${a r g s} = & ${get Variable Command} -Name args -ValueOnly -Scope Local -ErrorAction ${ignore preference}
        }
        if ($null -eq ${a r g s}) { ${a r g s} = @() }
    
        ${p s cmdlet} = & ${get Variable Command} -Name PSCmdlet -ValueOnly -Scope Local -ErrorAction ${ignore preference}
    
        ${session state} = if (${p s cmdlet}) { ${p s cmdlet}.SessionState }
    
        # @{mock call state} initialization is injected only into the begin block by the code that uses this prototype.
        Invoke-Mock -CommandName '#FUNCTIONNAME#' -ModuleName '#MODULENAME#' -BoundParameters $PSBoundParameters -ArgumentList ${a r g s} -CallerSessionState ${session state} -FromBlock '#BLOCK#' -MockCallState ${mock call state} #INPUT#
    }
    
    function Invoke-Mock {
        <#
            .SYNOPSIS
            This command is used by Pester's Mocking framework.  You do not need to call it directly.
        #>
    
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            [string]
            $CommandName,
    
            [Parameter(Mandatory = $true)]
            [hashtable] $MockCallState,
    
            [string]
            $ModuleName,
    
            [hashtable]
            $BoundParameters = @{},
    
            [object[]]
            $ArgumentList = @(),
    
            [object] $CallerSessionState,
    
            [ValidateSet('Begin', 'Process', 'End')]
            [string] $FromBlock,
    
            [object] $InputObject
        )
    
        $detectedModule = $ModuleName
        $mock = FindMock -CommandName $CommandName -ModuleName ([ref]$detectedModule)
    
        if ($null -eq $mock)
        {
            # If this ever happens, it's a bug in Pester.  The scriptBlock that calls Invoke-Mock should be removed at the same time as the entry in the mock table.
            throw "Internal error detected:  Mock for '$CommandName' in module '$ModuleName' was called, but does not exist in the mock table."
        }
    
        switch ($FromBlock)
        {
            Begin
            {
                $MockCallState['InputObjects'] = & $SafeCommands['New-Object'] System.Collections.ArrayList
                $MockCallState['ShouldExecuteOriginalCommand'] = $false
                $MockCallState['BeginBoundParameters'] = $BoundParameters.Clone()
                $MockCallState['BeginArgumentList'] = $ArgumentList
    
                return
            }
    
            Process
            {
                $block = $null
                if ($detectedModule -eq $ModuleName)
                {
                    $block = FindMatchingBlock -Mock $mock -BoundParameters $BoundParameters -ArgumentList $ArgumentList
                }
    
                if ($null -ne $block)
                {
                    ExecuteBlock -Block $block `
                                 -CommandName $CommandName `
                                 -ModuleName $ModuleName `
                                 -BoundParameters $BoundParameters `
                                 -ArgumentList $ArgumentList `
                                 -Mock $mock
    
                    return
                }
                else
                {
                    $MockCallState['ShouldExecuteOriginalCommand'] = $true
                    if ($null -ne $InputObject)
                    {
                        $null = $MockCallState['InputObjects'].AddRange(@($InputObject))
                    }
    
                    return
                }
            }
    
            End
            {
                if ($MockCallState['ShouldExecuteOriginalCommand'])
                {
                    if ($MockCallState['InputObjects'].Count -gt 0)
                    {
                        $scriptBlock = {
                            param ($Command, $ArgumentList, $BoundParameters, $InputObjects)
                            $InputObjects | & $Command @ArgumentList @BoundParameters
                        }
                    }
                    else
                    {
                        $scriptBlock = {
                            param ($Command, $ArgumentList, $BoundParameters, $InputObjects)
                            & $Command @ArgumentList @BoundParameters
                        }
                    }
    
                    $state = if ($CallerSessionState) { $CallerSessionState } else { $mock.SessionState }
    
                    Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $state
    
                    & $scriptBlock -Command $mock.OriginalCommand `
                                   -ArgumentList $MockCallState['BeginArgumentList'] `
                                   -BoundParameters $MockCallState['BeginBoundParameters'] `
                                   -InputObjects $MockCallState['InputObjects']
                }
            }
        }
    }
    
    function FindMock
    {
        param (
            [string] $CommandName,
            [ref] $ModuleName
        )
    
        $mock = $mockTable["$($ModuleName.Value)||$CommandName"]
    
        if ($null -eq $mock)
        {
            $mock = $mockTable["||$CommandName"]
            if ($null -ne $mock)
            {
                $ModuleName.Value = ''
            }
        }
    
        return $mock
    }
    
    function FindMatchingBlock
    {
        param (
            [object] $Mock,
            [hashtable] $BoundParameters = @{},
            [object[]] $ArgumentList = @()
        )
    
        for ($idx = $mock.Blocks.Length; $idx -gt 0; $idx--)
        {
            $block = $mock.Blocks[$idx - 1]
    
            $params = @{
                ScriptBlock     = $block.Filter
                BoundParameters = $BoundParameters
                ArgumentList    = $ArgumentList
                Metadata        = $mock.Metadata
            }
    
            if (Test-ParameterFilter @params)
            {
                return $block
            }
        }
    
        return $null
    }
    
    function ExecuteBlock
    {
        param (
            [object] $Block,
            [object] $Mock,
            [string] $CommandName,
            [string] $ModuleName,
            [hashtable] $BoundParameters = @{},
            [object[]] $ArgumentList = @()
        )
    
        $Block.Verifiable = $false
    
        # We set Scope to $null here to indicate the call came from the current Test Case.  It'll get assigned to a test group scope when Exit-MockScope is called.
        $Mock.CallHistory += @{CommandName = "$ModuleName||$CommandName"; BoundParams = $BoundParameters; Args = $ArgumentList; Scope = $null }
    
        $scriptBlock = {
            param (
                [Parameter(Mandatory = $true)]
                [scriptblock]
                ${Script Block},
    
                [hashtable]
                $___BoundParameters___ = @{},
    
                [object[]]
                $___ArgumentList___ = @(),
    
                [System.Management.Automation.CommandMetadata]
                ${Meta data},
    
                [System.Management.Automation.SessionState]
                ${Session State}
            )
    
            # This script block exists to hold variables without polluting the test script's current scope.
            # Dynamic parameters in functions, for some reason, only exist in $PSBoundParameters instead
            # of being assigned a local variable the way static parameters do.  By calling Set-DynamicParameterVariables,
            # we create these variables for the caller's use in a Parameter Filter or within the mock itself, and
            # by doing it inside this temporary script block, those variables don't stick around longer than they
            # should.
    
            Set-DynamicParameterVariables -SessionState ${Session State} -Parameters $___BoundParameters___ -Metadata ${Meta data}
            & ${Script Block} @___BoundParameters___ @___ArgumentList___
        }
    
        Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $mock.SessionState
        $splat = @{
            'Script Block' = $block.Mock
            '___ArgumentList___' = $ArgumentList
            '___BoundParameters___' = $BoundParameters
            'Meta data' = $mock.Metadata
            'Session State' = $mock.SessionState
        }
    
        & $scriptBlock @splat
    }
    
    function Invoke-InMockScope
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            [System.Management.Automation.SessionState]
            $SessionState,
    
            [Parameter(Mandatory = $true)]
            [scriptblock]
            $ScriptBlock,
    
            [Parameter(ValueFromRemainingArguments = $true)]
            [object[]]
            $ArgumentList = @()
        )
    
        if ($SessionState.Module)
        {
            $SessionState.Module.Invoke($ScriptBlock, $ArgumentList)
        }
        else
        {
            Set-ScriptBlockScope -ScriptBlock $ScriptBlock -SessionState $SessionState
            & $ScriptBlock @ArgumentList
        }
    }
    
    function Test-ParameterFilter
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            [scriptblock]
            $ScriptBlock,
    
            [System.Collections.IDictionary]
            $BoundParameters,
    
            [object[]]
            $ArgumentList,
    
            [System.Management.Automation.CommandMetadata]
            $Metadata
        )
    
        if ($null -eq $BoundParameters)   { $BoundParameters = @{} }
        if ($null -eq $ArgumentList)      { $ArgumentList = @() }
    
        $paramBlock = Get-ParamBlockFromBoundParameters -BoundParameters $BoundParameters -Metadata $Metadata
    
        $scriptBlockString = "
            $paramBlock
    
            Set-StrictMode -Off
            $ScriptBlock
        "
    
        $cmd = [scriptblock]::Create($scriptBlockString)
        Set-ScriptBlockScope -ScriptBlock $cmd -SessionState $pester.SessionState
    
        & $cmd @BoundParameters @ArgumentList
    }
    
    function Get-ParamBlockFromBoundParameters
    {
        param (
            [System.Collections.IDictionary] $BoundParameters,
            [System.Management.Automation.CommandMetadata] $Metadata
        )
    
        $params = foreach ($paramName in $BoundParameters.Keys)
        {
            if (IsCommonParameter -Name $paramName -Metadata $Metadata)
            {
                continue
            }
    
            "`${$paramName}"
        }
    
        $params = $params -join ','
    
        if ($null -ne $Metadata)
        {
            $cmdletBinding = [System.Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($Metadata)
        }
        else
        {
            $cmdletBinding = ''
        }
    
        return "$cmdletBinding param ($params)"
    }
    
    function IsCommonParameter
    {
        param (
            [string] $Name,
            [System.Management.Automation.CommandMetadata] $Metadata
        )
    
        if ($null -ne $Metadata)
        {
            if ([System.Management.Automation.Internal.CommonParameters].GetProperty($Name)) { return $true }
            if ($Metadata.SupportsShouldProcess -and [System.Management.Automation.Internal.ShouldProcessParameters].GetProperty($Name)) { return $true }
            if ($PSVersionTable.PSVersion.Major -ge 3 -and $Metadata.SupportsPaging -and [System.Management.Automation.PagingParameters].GetProperty($Name)) { return $true }
            if ($Metadata.SupportsTransactions -and [System.Management.Automation.Internal.TransactionParameters].GetProperty($Name)) { return $true }
        }
    
        return $false
    }
    
    function Set-DynamicParameterVariables
    {
        <#
            .SYNOPSIS
            This command is used by Pester's Mocking framework.  You do not need to call it directly.
        #>
    
        param (
            [Parameter(Mandatory = $true)]
            [System.Management.Automation.SessionState]
            $SessionState,
    
            [hashtable]
            $Parameters,
    
            [System.Management.Automation.CommandMetadata]
            $Metadata
        )
    
        if ($null -eq $Parameters) { $Parameters = @{} }
    
        foreach ($keyValuePair in $Parameters.GetEnumerator())
        {
            $variableName = $keyValuePair.Key
    
            if (-not (IsCommonParameter -Name $variableName -Metadata $Metadata))
            {
                if ($ExecutionContext.SessionState -eq $SessionState)
                {
                    & $SafeCommands['Set-Variable'] -Scope 1 -Name $variableName -Value $keyValuePair.Value -Force -Confirm:$false -WhatIf:$false
                }
                else
                {
                    $SessionState.PSVariable.Set($variableName, $keyValuePair.Value)
                }
            }
        }
    }
    
    function Get-DynamicParamBlock
    {
        param (
            [scriptblock] $ScriptBlock
        )
    
        if ($PSVersionTable.PSVersion.Major -le 2)
        {
            $flags = [System.Reflection.BindingFlags]'Instance, NonPublic'
            $dynamicParams = [scriptblock].GetField('_dynamicParams', $flags).GetValue($ScriptBlock)
    
            if ($null -ne $dynamicParams)
            {
                return $dynamicParams.ToString()
    
            }
        }
        else
        {
            if ($null -ne $ScriptBlock.Ast.Body.DynamicParamBlock)
            {
                $statements = $ScriptBlock.Ast.Body.DynamicParamBlock.Statements |
                              & $SafeCommands['Select-Object'] -ExpandProperty Extent |
                              & $SafeCommands['Select-Object'] -ExpandProperty Text
    
                return $statements -join "`r`n"
            }
        }
    }
    
    function Get-MockDynamicParameters
    {
        <#
            .SYNOPSIS
            This command is used by Pester's Mocking framework.  You do not need to call it directly.
        #>
    
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true, ParameterSetName = 'Cmdlet')]
            [string] $CmdletName,
    
            [Parameter(Mandatory = $true, ParameterSetName = 'Function')]
            [string] $FunctionName,
    
            [Parameter(ParameterSetName = 'Function')]
            [string] $ModuleName,
    
            [System.Collections.IDictionary] $Parameters,
    
            [object] $Cmdlet
        )
    
        switch ($PSCmdlet.ParameterSetName)
        {
            'Cmdlet'
            {
                Get-DynamicParametersForCmdlet -CmdletName $CmdletName -Parameters $Parameters
            }
    
            'Function'
            {
                Get-DynamicParametersForMockedFunction -FunctionName $FunctionName -ModuleName $ModuleName -Parameters $Parameters -Cmdlet $Cmdlet
            }
        }
    }
    
    function Get-DynamicParametersForCmdlet
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            [string] $CmdletName,
    
            [ValidateScript({
                if ($PSVersionTable.PSVersion.Major -ge 3 -and
                    $null -ne $_ -and
                    $_.GetType().FullName -ne 'System.Management.Automation.PSBoundParametersDictionary')
                {
                    throw 'The -Parameters argument must be a PSBoundParametersDictionary object ($PSBoundParameters).'
                }
    
                return $true
            })]
            [System.Collections.IDictionary] $Parameters
        )
    
        try
        {
            $command = & $SafeCommands['Get-Command'] -Name $CmdletName -CommandType Cmdlet -ErrorAction Stop
    
            if (@($command).Count -gt 1)
            {
                throw "Name '$CmdletName' resolved to multiple Cmdlets"
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError($_)
        }
    
        if ($null -eq $command.ImplementingType.GetInterface('IDynamicParameters', $true))
        {
            return
        }
    
        if ($PSVersionTable.PSVersion -ge '5.0.10586.122')
        {
            # Older version of PS required Reflection to do this.  It has run into problems on occasion with certain cmdlets,
            # such as ActiveDirectory and AzureRM, so we'll take advantage of the newer PSv5 engine features if at all possible.
    
            if ($null -eq $Parameters) { $paramsArg = @() } else { $paramsArg = @($Parameters) }
    
            $command = $ExecutionContext.InvokeCommand.GetCommand($CmdletName, [System.Management.Automation.CommandTypes]::Cmdlet, $paramsArg)
            $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
    
            foreach ($param in $command.Parameters.Values)
            {
                if (-not $param.IsDynamic) { continue }
                if ($Parameters.ContainsKey($param.Name)) { continue }
    
                $dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param.Name, $param.ParameterType, $param.Attributes)
                $paramDictionary.Add($param.Name, $dynParam)
            }
    
            return $paramDictionary
        }
        else
        {
            if ($null -eq $Parameters) { $Parameters = @{} }
    
            $cmdlet = & $SafeCommands['New-Object'] $command.ImplementingType.FullName
    
            $flags = [System.Reflection.BindingFlags]'Instance, Nonpublic'
            $context = $ExecutionContext.GetType().GetField('_context', $flags).GetValue($ExecutionContext)
            [System.Management.Automation.Cmdlet].GetProperty('Context', $flags).SetValue($cmdlet, $context, $null)
    
            foreach ($keyValuePair in $Parameters.GetEnumerator())
            {
                $property = $cmdlet.GetType().GetProperty($keyValuePair.Key)
                if ($null -eq $property -or -not $property.CanWrite) { continue }
    
                $isParameter = [bool]($property.GetCustomAttributes([System.Management.Automation.ParameterAttribute], $true))
                if (-not $isParameter) { continue }
    
                $property.SetValue($cmdlet, $keyValuePair.Value, $null)
            }
    
            try
            {
                # This unary comma is important in some cases.  On Windows 7 systems, the ActiveDirectory module cmdlets
                # return objects from this method which implement IEnumerable for some reason, and even cause PowerShell
                # to throw an exception when it tries to cast the object to that interface.
    
                # We avoid that problem by wrapping the result of GetDynamicParameters() in a one-element array with the
                # unary comma.  PowerShell enumerates that array instead of trying to enumerate the goofy object, and
                # everyone's happy.
    
                # Love the comma.  Don't delete it.  We don't have a test for this yet, unless we can get the AD module
                # on a Server 2008 R2 build server, or until we write some C# code to reproduce its goofy behavior.
    
                ,$cmdlet.GetDynamicParameters()
            }
            catch [System.NotImplementedException]
            {
                # Some cmdlets implement IDynamicParameters but then throw a NotImplementedException.  I have no idea why.  Ignore them.
            }
        }
    }
    
    function Get-DynamicParametersForMockedFunction
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            [string]
            $FunctionName,
    
            [string]
            $ModuleName,
    
            [System.Collections.IDictionary]
            $Parameters,
    
            [object]
            $Cmdlet
        )
    
        $mock = $mockTable["$ModuleName||$FunctionName"]
    
        if (-not $mock)
        {
            throw "Internal error detected:  Mock for '$FunctionName' in module '$ModuleName' was called, but does not exist in the mock table."
        }
    
        if ($mock.DynamicParamScriptBlock)
        {
            $splat = @{ 'P S Cmdlet' = $Cmdlet }
            return & $mock.DynamicParamScriptBlock @Parameters @splat
        }
    }
    
    function Test-IsClosure
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            [scriptblock]
            $ScriptBlock
        )
    
        $sessionStateInternal = Get-ScriptBlockScope -ScriptBlock $ScriptBlock
    
        $flags = [System.Reflection.BindingFlags]'Instance,NonPublic'
        $module = $sessionStateInternal.GetType().GetProperty('Module', $flags).GetValue($sessionStateInternal, $null)
    
        return (
            $null -ne $module -and
            $module.Name -match '^__DynamicModule_([a-f\d-]+)$' -and
            $null -ne ($matches[1] -as [guid])
        )
    }
    
  • tools\Functions\Mock.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    function FunctionUnderTest
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory=$false)]
            [string]
            $param1
        )
    
        return "I am a real world test"
    }
    
    function FunctionUnderTestWithoutParams([string]$param1) {
        return "I am a real world test with no params"
    }
    
    filter FilterUnderTest { $_ }
    
    function CommonParamFunction (
        [string] ${Uncommon},
        [switch]
        ${Verbose},
        [switch]
        ${Debug},
        [System.Management.Automation.ActionPreference]
        ${ErrorAction},
        [System.Management.Automation.ActionPreference]
        ${WarningAction},
        [System.String]
        ${ErrorVariable},
        [System.String]
        ${WarningVariable},
        [System.String]
        ${OutVariable},
        [System.Int32]
        ${OutBuffer} ){
        return "Please strip me of my common parameters. They are far too common."
    }
    
    function PipelineInputFunction {
        param(
            [Parameter(ValueFromPipeline=$True)]
            [int]$PipeInt1,
            [Parameter(ValueFromPipeline=$True)]
            [int[]]$PipeInt2,
            [Parameter(ValueFromPipeline=$True)]
            [string]$PipeStr,
            [Parameter(ValueFromPipelineByPropertyName=$True)]
            [int]$PipeIntProp,
            [Parameter(ValueFromPipelineByPropertyName=$True)]
            [int[]]$PipeArrayProp,
            [Parameter(ValueFromPipelineByPropertyName=$True)]
            [string]$PipeStringProp
        )
        begin{
            $p = 0
        }
        process {
            foreach($i in $input)
            {
                $p += 1
                write-output @{
                    index=$p;
                    val=$i;
                    PipeInt1=$PipeInt1;
                    PipeInt2=$PipeInt2;
                    PipeStr=$PipeStr;
                    PipeIntProp=$PipeIntProp;
                    PipeArrayProp=$PipeArrayProp;
                    PipeStringProp=$PipeStringProp;
                }
            }
        }
    }
    
    Describe "When calling Mock on existing function" {
        Mock FunctionUnderTest { return "I am the mock test that was passed $param1"}
    
        $result = FunctionUnderTest "boundArg"
    
        It "Should rename function under test" {
            $renamed = (Test-Path function:PesterIsMocking_FunctionUnderTest)
            $renamed | Should Be $true
        }
    
        It "Should Invoke the mocked script" {
            $result | Should Be "I am the mock test that was passed boundArg"
        }
    }
    
    Describe "When the caller mocks a command Pester uses internally" {
        Mock Write-Host { }
    
        Context "Context run when Write-Host is mocked" {
            It "does not make extra calls to the mocked command" {
                Write-Host 'Some String'
                Assert-MockCalled 'Write-Host' -Exactly 1
            }
    
            It "retains the correct mock count after the first test completes" {
                Assert-MockCalled 'Write-Host' -Exactly 1
            }
        }
    }
    
    Describe "When calling Mock on existing cmdlet" {
        Mock Get-Process {return "I am not Get-Process"}
    
        $result=Get-Process
    
        It "Should Invoke the mocked script" {
            $result | Should Be "I am not Get-Process"
        }
    
        It 'Should not resolve $args to the parent scope' {
            { $args = 'From', 'Parent', 'Scope'; Get-Process SomeName } | Should Not Throw
        }
    }
    
    Describe 'When calling Mock on an alias' {
        $originalPath = $env:path
    
        try
        {
            # Our TeamCity server has a dir.exe on the system path, and PowerShell v2 apparently finds that instead of the PowerShell alias first.
            # This annoying bit of code makes sure our test works as intended even when this is the case.
    
            $dirExe = Get-Command dir -CommandType Application -ErrorAction SilentlyContinue
            if ($null -ne $dirExe)
            {
                foreach ($app in $dirExe)
                {
                    $parent = (Split-Path $app.Path -Parent).TrimEnd('\')
                    $pattern = "^$([regex]::Escape($parent))\\?"
    
                    $env:path = $env:path -split ';' -notmatch $pattern -join ';'
                }
            }
    
            Mock dir {return 'I am not dir'}
    
            $result = dir
    
            It 'Should Invoke the mocked script' {
                $result | Should Be 'I am not dir'
            }
        }
        finally
        {
            $env:path = $originalPath
        }
    }
    
    Describe 'When calling Mock on an alias that refers to a function Pester can''t see' {
        It 'Mocks the aliased command successfully' {
            # This function is defined in a non-global scope; code inside the Pester module can't see it directly.
            function orig {'orig'}
            New-Alias 'ali' orig
    
            ali | Should Be 'orig'
    
            { mock ali {'mck'} } | Should Not Throw
    
            ali | Should Be 'mck'
        }
    }
    
    Describe 'When calling Mock on a filter' {
        Mock FilterUnderTest {return 'I am not FilterUnderTest'}
    
        $result = 'Yes I am' | FilterUnderTest
    
        It 'Should Invoke the mocked script' {
            $result | Should Be 'I am not FilterUnderTest'
        }
    }
    
    Describe 'When calling Mock on an external script' {
        $ps1File = New-Item 'TestDrive:\tempExternalScript.ps1' -ItemType File -Force
        $ps1File | Set-Content -Value "'I am tempExternalScript.ps1'"
    
        Mock 'TestDrive:\tempExternalScript.ps1' {return 'I am not tempExternalScript.ps1'}
    
        <#
            # Invoking the script using its absolute path is not supported
    
            $result = TestDrive:\tempExternalScript.ps1
            It 'Should Invoke the absolute-path-qualified mocked script using just the script name' {
                $result | Should Be 'I am not tempExternalScript.ps1'
            }
    
            $result = & TestDrive:\tempExternalScript.ps1
            It 'Should Invoke the absolute-path-qualified mocked script using the command-invocation operator (&)' {
                $result | Should Be 'I am not tempExternalScript.ps1'
            }
    
            $result = . TestDrive:\tempExternalScript.ps1
            It 'Should Invoke the absolute-path-qualified mocked script using dot source notation' {
                $result | Should Be 'I am not tempExternalScript.ps1'
            }
        #>
    
        Push-Location TestDrive:\
    
        try
        {
            $result = tempExternalScript.ps1
            It 'Should Invoke the mocked script using just the script name' {
                $result | Should Be 'I am not tempExternalScript.ps1'
            }
    
            $result = & tempExternalScript.ps1
            It 'Should Invoke the mocked script using the command-invocation operator' {
                #the command invocation operator is (&). Moved this to comment because it breaks the continuous builds.
                #there is issue for this on GH
    
                $result | Should Be 'I am not tempExternalScript.ps1'
            }
    
            $result = . tempExternalScript.ps1
            It 'Should Invoke the mocked script using dot source notation' {
                $result | Should Be 'I am not tempExternalScript.ps1'
            }
    
            <#
                # Invoking the script using only its relative path is not supported
    
                $result = .\tempExternalScript.ps1
                It 'Should Invoke the relative-path-qualified mocked script' {
                    $result | Should Be 'I am not tempExternalScript.ps1'
                }
            #>
    
        }
        finally
        {
            Pop-Location
        }
    
        Remove-Item $ps1File -Force -ErrorAction SilentlyContinue
    }
    
    Describe 'When calling Mock on an application command' {
        Mock schtasks.exe {return 'I am not schtasks.exe'}
    
        $result = schtasks.exe
    
        It 'Should Invoke the mocked script' {
            $result | Should Be 'I am not schtasks.exe'
        }
    }
    
    Describe "When calling Mock in the Describe block" {
        Mock Out-File {return "I am not Out-File"}
    
        It "Should mock Out-File successfully" {
            $outfile = "test" | Out-File "TestDrive:\testfile.txt"
            $outfile | Should Be "I am not Out-File"
        }
    }
    
    Describe "When calling Mock on existing cmdlet to handle pipelined input" {
        Mock Get-ChildItem {
            if($_ -eq 'a'){
                return "AA"
            }
            if($_ -eq 'b'){
                return "BB"
            }
        }
    
        $result = ''
        "a", "b" | Get-ChildItem | % { $result += $_ }
    
        It "Should process the pipeline in the mocked script" {
            $result | Should Be "AABB"
        }
    }
    
    Describe "When calling Mock on existing cmdlet with Common params" {
        Mock CommonParamFunction
    
        $result=[string](Get-Content function:\CommonParamFunction)
    
        It "Should strip verbose" {
            $result.contains("`${Verbose}") | Should Be $false
        }
        It "Should strip Debug" {
            $result.contains("`${Debug}") | Should Be $false
        }
        It "Should strip ErrorAction" {
            $result.contains("`${ErrorAction}") | Should Be $false
        }
        It "Should strip WarningAction" {
            $result.contains("`${WarningAction}") | Should Be $false
        }
        It "Should strip ErrorVariable" {
            $result.contains("`${ErrorVariable}") | Should Be $false
        }
        It "Should strip WarningVariable" {
            $result.contains("`${WarningVariable}") | Should Be $false
        }
        It "Should strip OutVariable" {
            $result.contains("`${OutVariable}") | Should Be $false
        }
        It "Should strip OutBuffer" {
            $result.contains("`${OutBuffer}") | Should Be $false
        }
        It "Should not strip an Uncommon param" {
            $result.contains("`${Uncommon}") | Should Be $true
        }
    }
    
    Describe "When calling Mock on non-existing function" {
        try{
            Mock NotFunctionUnderTest {return}
        } Catch {
            $result=$_
        }
    
        It "Should throw correct error" {
            $result.Exception.Message | Should Be "Could not find command NotFunctionUnderTest"
        }
    }
    
    Describe 'When calling Mock, StrictMode is enabled, and variables are used in the ParameterFilter' {
        Set-StrictMode -Version Latest
    
        $result = $null
        $testValue = 'test'
    
        try
        {
            Mock FunctionUnderTest { 'I am the mock' } -ParameterFilter { $param1 -eq $testValue }
        }
        catch
        {
            $result = $_
        }
    
        It 'Does not throw an error when testing the parameter filter' {
            $result | Should Be $null
        }
    
        It 'Calls the mock properly' {
            FunctionUnderTest $testValue | Should Be 'I am the mock'
        }
    
        It 'Properly asserts the mock was called when there is a variable in the parameter filter' {
            Assert-MockCalled FunctionUnderTest -Exactly 1 -ParameterFilter { $param1 -eq $testValue }
        }
    }
    
    Describe "When calling Mock on existing function without matching bound params" {
        Mock FunctionUnderTest {return "fake results"} -parameterFilter {$param1 -eq "test"}
    
        $result=FunctionUnderTest "badTest"
    
        It "Should redirect to real function" {
            $result | Should Be "I am a real world test"
        }
    }
    
    Describe "When calling Mock on existing function with matching bound params" {
        Mock FunctionUnderTest {return "fake results"} -parameterFilter {$param1 -eq "badTest"}
    
        $result=FunctionUnderTest "badTest"
    
        It "Should return mocked result" {
            $result | Should Be "fake results"
        }
    }
    
    Describe "When calling Mock on existing function without matching unbound arguments" {
        Mock FunctionUnderTestWithoutParams {return "fake results"} -parameterFilter {$param1 -eq "test" -and $args[0] -eq 'notArg0'}
    
        $result=FunctionUnderTestWithoutParams -param1 "test" "arg0"
    
        It "Should redirect to real function" {
            $result | Should Be "I am a real world test with no params"
        }
    }
    
    Describe "When calling Mock on existing function with matching unbound arguments" {
        Mock FunctionUnderTestWithoutParams {return "fake results"} -parameterFilter {$param1 -eq "badTest" -and $args[0] -eq 'arg0'}
    
        $result=FunctionUnderTestWithoutParams "badTest" "arg0"
    
        It "Should return mocked result" {
            $result | Should Be "fake results"
        }
    }
    
    Describe 'When calling Mock on a function that has no parameters' {
        function Test-Function { }
        Mock Test-Function { return $args.Count }
    
        It 'Sends the $args variable properly with 2+ elements' {
            Test-Function 1 2 3 4 5 | Should Be 5
        }
    
        It 'Sends the $args variable properly with 1 element' {
            Test-Function 1 | Should Be 1
        }
    
        It 'Sends the $args variable properly with 0 elements' {
            Test-Function | Should Be 0
        }
    }
    
    Describe "When calling Mock on cmdlet Used by Mock" {
        Mock Set-Item {return "I am not Set-Item"}
        Mock Set-Item {return "I am not Set-Item"}
    
        $result = Set-Item "mypath" -value "value"
    
        It "Should Invoke the mocked script" {
            $result | Should Be "I am not Set-Item"
        }
    }
    
    Describe "When calling Mock on More than one command" {
        Mock Invoke-Command {return "I am not Invoke-Command"}
        Mock FunctionUnderTest {return "I am the mock test"}
    
        $result = Invoke-Command {return "yes I am"}
        $result2 = FunctionUnderTest
    
        It "Should Invoke the mocked script for the first Mock" {
            $result | Should Be "I am not Invoke-Command"
        }
        It "Should Invoke the mocked script for the second Mock" {
            $result2 | Should Be "I am the mock test"
        }
    }
    
    Describe 'When calling Mock on a module-internal function.' {
        New-Module -Name TestModule {
            function InternalFunction { 'I am the internal function' }
            function PublicFunction   { InternalFunction }
            function PublicFunctionThatCallsExternalCommand { Start-Sleep 0 }
    
            function FuncThatOverwritesExecutionContext {
                param ($ExecutionContext)
    
                InternalFunction
            }
    
            Export-ModuleMember -Function PublicFunction, PublicFunctionThatCallsExternalCommand, FuncThatOverwritesExecutionContext
        } | Import-Module -Force
    
        New-Module -Name TestModule2 {
            function InternalFunction { 'I am the second module internal function' }
            function InternalFunction2 { 'I am the second module, second function' }
            function PublicFunction   { InternalFunction }
            function PublicFunction2 { InternalFunction2 }
    
            function FuncThatOverwritesExecutionContext {
                param ($ExecutionContext)
    
                InternalFunction
            }
    
            function ScopeTest {
                return Get-CallerModuleName
            }
    
            function Get-CallerModuleName {
                [CmdletBinding()]
                param ( )
    
                return $PSCmdlet.SessionState.Module.Name
            }
    
            Export-ModuleMember -Function PublicFunction, PublicFunction2, FuncThatOverwritesExecutionContext, ScopeTest
        } | Import-Module -Force
    
        It 'Should fail to call the internal module function' {
            { TestModule\InternalFunction } | Should Throw
        }
    
        It 'Should call the actual internal module function from the public function' {
            TestModule\PublicFunction | Should Be 'I am the internal function'
        }
    
        Context 'Using Mock -ModuleName "ModuleName" "CommandName" syntax' {
            Mock -ModuleName TestModule InternalFunction { 'I am the mock test' }
    
            It 'Should call the mocked function' {
                TestModule\PublicFunction | Should Be 'I am the mock test'
            }
    
            Mock -ModuleName TestModule Start-Sleep { }
    
            It 'Should mock calls to external functions from inside the module' {
                PublicFunctionThatCallsExternalCommand
    
                Assert-MockCalled -ModuleName TestModule Start-Sleep -Exactly 1
            }
    
            Mock -ModuleName TestModule2 InternalFunction -ParameterFilter { $args[0] -eq 'Test' } {
                "I'm the mock who's been passed parameter Test"
            }
    
            It 'Should only call mocks within the same module' {
                TestModule2\PublicFunction | Should Be 'I am the second module internal function'
            }
    
            Mock -ModuleName TestModule2 InternalFunction2 {
                InternalFunction 'Test'
            }
    
            It 'Should call mocks from inside another mock' {
                TestModule2\PublicFunction2 | Should Be "I'm the mock who's been passed parameter Test"
            }
    
            It 'Should work even if the function is weird and steps on the automatic $ExecutionContext variable.' {
                TestModule2\FuncThatOverwritesExecutionContext | Should Be 'I am the second module internal function'
                TestModule\FuncThatOverwritesExecutionContext | Should Be 'I am the mock test'
            }
    
            Mock -ModuleName TestModule2 Get-CallerModuleName -ParameterFilter { $false }
    
            It 'Should call the original command from the proper scope if no parameter filters match' {
                TestModule2\ScopeTest | Should Be 'TestModule2'
            }
    
            Mock -ModuleName TestModule2 Get-Content { }
    
            It 'Does not trigger the mocked Get-Content from Pester internals' {
                Mock -ModuleName TestModule2 Get-CallerModuleName -ParameterFilter { $false }
                Assert-MockCalled -ModuleName TestModule2 Get-Content -Times 0 -Scope It
            }
        }
    
        AfterAll {
            Remove-Module TestModule -Force
            Remove-Module TestModule2 -Force
        }
    }
    
    Describe "When Applying multiple Mocks on a single command" {
        Mock FunctionUnderTest {return "I am the first mock test"} -parameterFilter {$param1 -eq "one"}
        Mock FunctionUnderTest {return "I am the Second mock test"} -parameterFilter {$param1 -eq "two"}
    
        $result = FunctionUnderTest "one"
        $result2= FunctionUnderTest "two"
    
        It "Should Invoke the mocked script for the first Mock" {
            $result | Should Be "I am the first mock test"
        }
        It "Should Invoke the mocked script for the second Mock" {
            $result2 | Should Be "I am the Second mock test"
        }
    }
    
    Describe "When Applying multiple Mocks with filters on a single command where both qualify" {
        Mock FunctionUnderTest {return "I am the first mock test"} -parameterFilter {$param1.Length -gt 0 }
        Mock FunctionUnderTest {return "I am the Second mock test"} -parameterFilter {$param1 -gt 1 }
    
        $result = FunctionUnderTest "one"
    
        It "The last Mock should win" {
            $result | Should Be "I am the Second mock test"
        }
    }
    
    Describe "When Applying multiple Mocks on a single command where one has no filter" {
        Mock FunctionUnderTest {return "I am the first mock test"} -parameterFilter {$param1 -eq "one"}
        Mock FunctionUnderTest {return "I am the paramless mock test"}
        Mock FunctionUnderTest {return "I am the Second mock test"} -parameterFilter {$param1 -eq "two"}
    
        $result = FunctionUnderTest "one"
        $result2= FunctionUnderTest "three"
    
        It "The parameterless mock is evaluated last" {
            $result | Should Be "I am the first mock test"
        }
    
        It "The parameterless mock will be applied if no other wins" {
            $result2 | Should Be "I am the paramless mock test"
        }
    }
    
    Describe "When Creating a Verifiable Mock that is not called" {
        Context "In the test script's scope" {
            Mock FunctionUnderTest {return "I am a verifiable test"} -Verifiable -parameterFilter {$param1 -eq "one"}
            FunctionUnderTest "three" | Out-Null
    
            try {
                Assert-VerifiableMocks
            } Catch {
                $result=$_
            }
    
            It "Should throw" {
                $result.Exception.Message | Should Be "`r`n Expected FunctionUnderTest to be called with `$param1 -eq `"one`""
            }
        }
    
        Context "In a module's scope" {
            New-Module -Name TestModule -ScriptBlock {
                function ModuleFunctionUnderTest { return 'I am the function under test in a module' }
            } | Import-Module -Force
    
            Mock -ModuleName TestModule ModuleFunctionUnderTest {return "I am a verifiable test"} -Verifiable -parameterFilter {$param1 -eq "one"}
            TestModule\ModuleFunctionUnderTest "three" | Out-Null
    
            try {
                Assert-VerifiableMocks
            } Catch {
                $result=$_
            }
    
            It "Should throw" {
                $result.Exception.Message | Should Be "`r`n Expected ModuleFunctionUnderTest in module TestModule to be called with `$param1 -eq `"one`""
            }
    
            AfterAll {
                Remove-Module TestModule -Force
            }
        }
    }
    
    Describe "When Creating a Verifiable Mock that is called" {
        Mock FunctionUnderTest -Verifiable -parameterFilter {$param1 -eq "one"}
        FunctionUnderTest "one"
        It "Assert-VerifiableMocks Should not throw" {
            { Assert-VerifiableMocks } | Should Not Throw
        }
    }
    
    Describe "When Calling Assert-MockCalled 0 without exactly" {
        Mock FunctionUnderTest {}
        FunctionUnderTest "one"
    
        try {
            Assert-MockCalled FunctionUnderTest 0
        } Catch {
            $result=$_
        }
    
        It "Should throw if mock was called" {
            $result.Exception.Message | Should Be "Expected FunctionUnderTest to be called 0 times exactly but was called 1 times"
        }
    
        It "Should not throw if mock was not called" {
            Assert-MockCalled FunctionUnderTest 0 { $param1 -eq "stupid" }
        }
    }
    
    Describe "When Calling Assert-MockCalled with exactly" {
        Mock FunctionUnderTest {}
        FunctionUnderTest "one"
        FunctionUnderTest "one"
    
        try {
            Assert-MockCalled FunctionUnderTest -exactly 3
        } Catch {
            $result=$_
        }
    
        It "Should throw if mock was not called the number of times specified" {
            $result.Exception.Message | Should Be "Expected FunctionUnderTest to be called 3 times exactly but was called 2 times"
        }
    
        It "Should not throw if mock was called the number of times specified" {
            Assert-MockCalled FunctionUnderTest -exactly 2 { $param1 -eq "one" }
        }
    }
    
    Describe "When Calling Assert-MockCalled without exactly" {
        Mock FunctionUnderTest {}
        FunctionUnderTest "one"
        FunctionUnderTest "one"
        FunctionUnderTest "two"
    
        It "Should throw if mock was not called at least the number of times specified" {
            $scriptBlock = { Assert-MockCalled FunctionUnderTest 4 }
            $scriptBlock | Should Throw "Expected FunctionUnderTest to be called at least 4 times but was called 3 times"
        }
    
        It "Should not throw if mock was called at least the number of times specified" {
            Assert-MockCalled FunctionUnderTest
        }
    
        It "Should not throw if mock was called at exactly the number of times specified" {
            Assert-MockCalled FunctionUnderTest 2 { $param1 -eq "one" }
        }
    
        It "Should throw an error if any non-matching calls to the mock are made, and the -ExclusiveFilter parameter is used" {
            $scriptBlock = { Assert-MockCalled FunctionUnderTest -ExclusiveFilter { $param1 -eq 'one' } }
            $scriptBlock | Should Throw '1 non-matching calls were made'
        }
    }
    
    Describe "Using Pester Scopes (Describe,Context,It)" {
        Mock FunctionUnderTest {return "I am the first mock test"} -parameterFilter {$param1 -eq "one"}
        Mock FunctionUnderTest {return "I am the paramless mock test"}
    
        Context "When in the first context" {
            It "should mock Describe scoped paramles mock" {
                FunctionUnderTest | should be "I am the paramless mock test"
            }
            It "should mock Describe scoped single param mock" {
                FunctionUnderTest "one" | should be "I am the first mock test"
            }
        }
    
        Context "When in the second context" {
            It "should mock Describe scoped paramles mock again" {
                FunctionUnderTest | should be "I am the paramless mock test"
            }
            It "should mock Describe scoped single param mock again" {
                FunctionUnderTest "one" | should be "I am the first mock test"
            }
        }
    
        Context "When using mocks in both scopes" {
            Mock FunctionUnderTestWithoutParams {return "I am the other function"}
    
            It "should mock Describe scoped mock." {
                FunctionUnderTest | should be "I am the paramless mock test"
            }
            It "should mock Context scoped mock." {
                FunctionUnderTestWithoutParams | should be "I am the other function"
            }
        }
    
        Context "When context hides a describe mock" {
            Mock FunctionUnderTest {return "I am the context mock"}
            Mock FunctionUnderTest {return "I am the parameterized context mock"} -parameterFilter {$param1 -eq "one"}
    
            It "should use the context paramles mock" {
                FunctionUnderTest | should be "I am the context mock"
            }
            It "should use the context parameterized mock" {
                FunctionUnderTest "one" | should be "I am the parameterized context mock"
            }
        }
    
        Context "When context no longer hides a describe mock" {
            It "should use the describe mock" {
                FunctionUnderTest | should be "I am the paramless mock test"
            }
    
            It "should use the describe parameterized mock" {
                FunctionUnderTest "one" | should be "I am the first mock test"
            }
        }
    
        Context 'When someone calls Mock from inside an It block' {
            Mock FunctionUnderTest { return 'I am the context mock' }
    
            It 'Sets the mock' {
                Mock FunctionUnderTest { return 'I am the It mock' }
            }
    
            It 'Leaves the mock active in the parent scope' {
                FunctionUnderTest | Should Be 'I am the It mock'
            }
        }
    }
    
    Describe 'Testing mock history behavior from each scope' {
        function MockHistoryChecker { }
        Mock MockHistoryChecker { 'I am the describe mock.' }
    
        Context 'Without overriding the mock in lower scopes' {
            It "Reports that zero calls have been made to in the describe scope" {
                Assert-MockCalled MockHistoryChecker -Exactly 0 -Scope Describe
            }
    
            It 'Calls the describe mock' {
                MockHistoryChecker | Should Be 'I am the describe mock.'
            }
    
            It "Reports that zero calls have been made in an It block, after a context-scoped call" {
                Assert-MockCalled MockHistoryChecker -Exactly 0 -Scope It
            }
    
            It "Reports one Context-scoped call" {
                Assert-MockCalled MockHistoryChecker -Exactly 1
            }
    
            It "Reports one Describe-scoped call" {
                Assert-MockCalled MockHistoryChecker -Exactly 1 -Scope Describe
            }
        }
    
        Context 'After exiting the previous context' {
            It 'Reports zero context-scoped calls in the new context.' {
                Assert-MockCalled MockHistoryChecker -Exactly 0
            }
    
            It 'Reports one describe-scoped call from the previous context' {
                Assert-MockCalled MockHistoryChecker -Exactly 1 -Scope Describe
            }
        }
    
        Context 'While overriding mocks in lower scopes' {
            Mock MockHistoryChecker { 'I am the context mock.' }
    
            It 'Calls the context mock' {
                MockHistoryChecker | Should Be 'I am the context mock.'
            }
    
            It 'Reports one context-scoped call' {
                Assert-MockCalled MockHistoryChecker -Exactly 1
            }
    
            It 'Reports two describe-scoped calls, even when one is an override mock in a lower scope' {
                Assert-MockCalled MockHistoryChecker -Exactly 2 -Scope Describe
            }
    
            It 'Calls an It-scoped mock' {
                Mock MockHistoryChecker { 'I am the It mock.' }
                MockHistoryChecker | Should Be 'I am the It mock.'
            }
    
            It 'Reports 2 context-scoped calls' {
                Assert-MockCalled MockHistoryChecker -Exactly 2
            }
    
            It 'Reports 3 describe-scoped calls' {
                Assert-MockCalled MockHistoryChecker -Exactly 3 -Scope Describe
            }
        }
    
        It 'Reports 3 describe-scoped calls using the default scope in a Describe block' {
            Assert-MockCalled MockHistoryChecker -Exactly 3
        }
    }
    
    Describe "Using a single no param Describe" {
        Mock FunctionUnderTest {return "I am the describe mock test"}
    
        Context "With a context mocking the same function with no params"{
            Mock FunctionUnderTest {return "I am the context mock test"}
            It "Should use the context mock" {
                FunctionUnderTest | should be "I am the context mock test"
            }
        }
    }
    
    Describe 'Dot Source Test' {
        # This test is only meaningful if this test file is dot-sourced in the global scope.  If it's executed without
        # dot-sourcing or run by Invoke-Pester, there's no problem.
    
        function TestFunction { Test-Path -Path 'Test' }
        Mock Test-Path { }
    
        $null = TestFunction
    
        It "Calls the mock with parameter 'Test'" {
            Assert-MockCalled Test-Path -Exactly 1 -ParameterFilter { $Path -eq 'Test' }
        }
    
        It "Doesn't call the mock with any other parameters" {
            InModuleScope Pester {
                $global:calls = $mockTable['||Test-Path'].CallHistory
            }
            Assert-MockCalled Test-Path -Exactly 0 -ParameterFilter { $Path -ne 'Test' }
        }
    }
    
    Describe 'Mocking Cmdlets with dynamic parameters' {
        $mockWith = { if (-not $CodeSigningCert) { throw 'CodeSigningCert variable not found, or set to false!' } }
        Mock Get-ChildItem -MockWith $mockWith -ParameterFilter { [bool]$CodeSigningCert }
    
        It 'Allows calls to be made with dynamic parameters (including parameter filters)' {
            { Get-ChildItem -Path Cert:\ -CodeSigningCert } | Should Not Throw
            Assert-MockCalled Get-ChildItem
        }
    }
    
    Describe 'Mocking functions with dynamic parameters' {
        Context 'Dynamicparam block that uses the variables of static parameters in its logic' {
            # Get-Greeting sample function borrowed and modified from Bartek Bielawski's
            # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/
    
            function Get-Greeting {
                [CmdletBinding()]
                param (
                    [string] $Name
                )
    
                DynamicParam {
                    if ($Name -cmatch '\b[a-z]') {
                        $Attributes = New-Object Management.Automation.ParameterAttribute
                        $Attributes.ParameterSetName = "__AllParameterSets"
                        $Attributes.Mandatory = $false
    
                        $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute]
                        $AttributeCollection.Add($Attributes)
    
                        $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection)
    
                        $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
                        $ParamDictionary.Add("Capitalize", $Dynamic)
                        $ParamDictionary
                    }
                }
    
                end
                {
                    if($PSBoundParameters.Capitalize) {
                        $Name = [regex]::Replace(
                            $Name,
                            '\b\w',
                            { $args[0].Value.ToUpper() }
                        )
                    }
    
                    "Welcome $Name!"
                }
            }
    
            $mockWith = { if (-not $Capitalize) { throw 'Capitalize variable not found, or set to false!' } }
            Mock Get-Greeting -MockWith $mockWith -ParameterFilter { [bool]$Capitalize }
    
            It 'Allows calls to be made with dynamic parameters (including parameter filters)' {
                { Get-Greeting -Name lowercase -Capitalize } | Should Not Throw
                Assert-MockCalled Get-Greeting
            }
    
            $Capitalize = $false
    
            It 'Sets the dynamic parameter variable properly' {
                { Get-Greeting -Name lowercase -Capitalize } | Should Not Throw
                Assert-MockCalled Get-Greeting -Scope It
            }
        }
    
        Context 'When the mocked command is in a module' {
            New-Module -Name TestModule {
                function PublicFunction { Get-Greeting -Name lowercase -Capitalize }
    
                $script:DoDynamicParam = $true
    
                # Get-Greeting sample function borrowed and modified from Bartek Bielawski's
                # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/
    
                function script:Get-Greeting {
                    [CmdletBinding()]
                    param (
                        [string] $Name
                    )
    
                    DynamicParam {
                        # This check is here to make sure the mocked version can still work if the
                        # original function's dynamicparam block relied on script-scope variables.
                        if (-not $script:DoDynamicParam) { return }
    
                        if ($Name -cmatch '\b[a-z]') {
                            $Attributes = New-Object Management.Automation.ParameterAttribute
                            $Attributes.ParameterSetName = "__AllParameterSets"
                            $Attributes.Mandatory = $false
    
                            $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute]
                            $AttributeCollection.Add($Attributes)
    
                            $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection)
    
                            $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
                            $ParamDictionary.Add("Capitalize", $Dynamic)
                            $ParamDictionary
                        }
                    }
    
                    end
                    {
                        if($PSBoundParameters.Capitalize) {
                            $Name = [regex]::Replace(
                                $Name,
                                '\b\w',
                                { $args[0].Value.ToUpper() }
                            )
                        }
    
                        "Welcome $Name!"
                    }
                }
            } | Import-Module -Force
    
            $mockWith = { if (-not $Capitalize) { throw 'Capitalize variable not found, or set to false!' } }
            Mock Get-Greeting -MockWith $mockWith -ModuleName TestModule -ParameterFilter { [bool]$Capitalize }
    
            It 'Allows calls to be made with dynamic parameters (including parameter filters)' {
                { TestModule\PublicFunction } | Should Not Throw
                Assert-MockCalled Get-Greeting -ModuleName TestModule
            }
    
            AfterAll {
                Remove-Module TestModule -Force
            }
        }
    
        Context 'When the mocked command has mandatory parameters that are passed in via the pipeline' {
            # Get-Greeting sample function borrowed and modified from Bartek Bielawski's
            # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/
    
            function Get-Greeting2 {
                [CmdletBinding()]
                param (
                    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
                    [string] $MandatoryParam,
    
                    [string] $Name
                )
    
                DynamicParam {
                    if ($Name -cmatch '\b[a-z]') {
                        $Attributes = New-Object Management.Automation.ParameterAttribute
                        $Attributes.ParameterSetName = "__AllParameterSets"
                        $Attributes.Mandatory = $false
    
                        $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute]
                        $AttributeCollection.Add($Attributes)
    
                        $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection)
    
                        $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
                        $ParamDictionary.Add("Capitalize", $Dynamic)
                        $ParamDictionary
                    }
                }
    
                end
                {
                    if($PSBoundParameters.Capitalize) {
                        $Name = [regex]::Replace(
                            $Name,
                            '\b\w',
                            { $args[0].Value.ToUpper() }
                        )
                    }
    
                    "Welcome $Name!"
                }
            }
    
            Mock Get-Greeting2 { 'Mocked' } -ParameterFilter { [bool]$Capitalize }
            $hash = @{ Result = $null }
            $scriptBlock = { $hash.Result = 'Mandatory' | Get-Greeting2 -Name test -Capitalize }
    
            It 'Should successfully call the mock and generate the dynamic parameters' {
                $scriptBlock | Should Not Throw
                $hash.Result | Should Be 'Mocked'
            }
        }
    
        Context 'When the mocked command has parameter sets that are ambiguous at the time the dynamic param block is executed' {
            # Get-Greeting sample function borrowed and modified from Bartek Bielawski's
            # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/
    
            function Get-Greeting3 {
                [CmdletBinding()]
                param (
                    [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'One')]
                    [string] $One,
    
                    [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Two')]
                    [string] $Two,
    
                    [string] $Name
                )
    
                DynamicParam {
                    if ($Name -cmatch '\b[a-z]') {
                        $Attributes = New-Object Management.Automation.ParameterAttribute
                        $Attributes.ParameterSetName = "__AllParameterSets"
                        $Attributes.Mandatory = $false
    
                        $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute]
                        $AttributeCollection.Add($Attributes)
    
                        $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection)
    
                        $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
                        $ParamDictionary.Add("Capitalize", $Dynamic)
                        $ParamDictionary
                    }
                }
    
                end
                {
                    if($PSBoundParameters.Capitalize) {
                        $Name = [regex]::Replace(
                            $Name,
                            '\b\w',
                            { $args[0].Value.ToUpper() }
                        )
                    }
    
                    "Welcome $Name!"
                }
            }
    
            Mock Get-Greeting3 { 'Mocked' } -ParameterFilter { [bool]$Capitalize }
            $hash = @{ Result = $null }
            $scriptBlock = { $hash.Result = New-Object psobject -Property @{ One = 'One' } | Get-Greeting3 -Name test -Capitalize }
    
            It 'Should successfully call the mock and generate the dynamic parameters' {
                $scriptBlock | Should Not Throw
                $hash.Result | Should Be 'Mocked'
            }
        }
    
        Context 'When the mocked command''s dynamicparam block depends on the contents of $PSBoundParameters' {
            # Get-Greeting sample function borrowed and modified from Bartek Bielawski's
            # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/
    
            function Get-Greeting4 {
                [CmdletBinding()]
                param (
                    [string] $Name
                )
    
                DynamicParam {
                    if ($PSBoundParameters['Name'] -cmatch '\b[a-z]') {
                        $Attributes = New-Object Management.Automation.ParameterAttribute
                        $Attributes.ParameterSetName = "__AllParameterSets"
                        $Attributes.Mandatory = $false
    
                        $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute]
                        $AttributeCollection.Add($Attributes)
    
                        $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection)
    
                        $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
                        $ParamDictionary.Add("Capitalize", $Dynamic)
                        $ParamDictionary
                    }
                }
    
                end
                {
                    if($PSBoundParameters.Capitalize) {
                        $Name = [regex]::Replace(
                            $Name,
                            '\b\w',
                            { $args[0].Value.ToUpper() }
                        )
                    }
    
                    "Welcome $Name!"
                }
            }
    
            Mock Get-Greeting4 { 'Mocked' } -ParameterFilter { [bool]$Capitalize }
            $hash = @{ Result = $null }
            $scriptBlock = { $hash.Result = Get-Greeting4 -Name test -Capitalize }
    
            It 'Should successfully call the mock and generate the dynamic parameters' {
                $scriptBlock | Should Not Throw
                $hash.Result | Should Be 'Mocked'
            }
        }
    
        Context 'When the mocked command''s dynamicparam block depends on the contents of $PSCmdlet.ParameterSetName' {
            # Get-Greeting sample function borrowed and modified from Bartek Bielawski's
            # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/
    
            function Get-Greeting5 {
                [CmdletBinding(DefaultParameterSetName = 'One')]
                param (
                    [string] $Name,
    
                    [Parameter(ParameterSetName = 'Two')]
                    [string] $Two
                )
    
                DynamicParam {
                    if ($PSCmdlet.ParameterSetName -eq 'Two' -and $Name -cmatch '\b[a-z]') {
                        $Attributes = New-Object Management.Automation.ParameterAttribute
                        $Attributes.ParameterSetName = "__AllParameterSets"
                        $Attributes.Mandatory = $false
    
                        $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute]
                        $AttributeCollection.Add($Attributes)
    
                        $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection)
    
                        $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
                        $ParamDictionary.Add("Capitalize", $Dynamic)
                        $ParamDictionary
                    }
                }
    
                end
                {
                    if($PSBoundParameters.Capitalize) {
                        $Name = [regex]::Replace(
                            $Name,
                            '\b\w',
                            { $args[0].Value.ToUpper() }
                        )
                    }
    
                    "Welcome $Name!"
                }
            }
    
            Mock Get-Greeting5 { 'Mocked' } -ParameterFilter { [bool]$Capitalize }
            $hash = @{ Result = $null }
            $scriptBlock = { $hash.Result = Get-Greeting5 -Two 'Two' -Name test -Capitalize }
    
            It 'Should successfully call the mock and generate the dynamic parameters' {
                $scriptBlock | Should Not Throw
                $hash.Result | Should Be 'Mocked'
            }
        }
    }
    
    Describe 'Mocking Cmdlets with dynamic parameters in a module' {
        New-Module -Name TestModule {
            function PublicFunction   { Get-ChildItem -Path Cert:\ -CodeSigningCert }
        } | Import-Module -Force
    
        $mockWith = { if (-not $CodeSigningCert) { throw 'CodeSigningCert variable not found, or set to false!' } }
        Mock Get-ChildItem -MockWith $mockWith -ModuleName TestModule -ParameterFilter { [bool]$CodeSigningCert }
    
        It 'Allows calls to be made with dynamic parameters (including parameter filters)' {
            { TestModule\PublicFunction } | Should Not Throw
            Assert-MockCalled Get-ChildItem -ModuleName TestModule
        }
    
        AfterAll {
            Remove-Module TestModule -Force
        }
    }
    
    Describe 'DynamicParam blocks in other scopes' {
        New-Module -Name TestModule1 {
            $script:DoDynamicParam = $true
    
            function DynamicParamFunction {
                [CmdletBinding()]
                param ( )
    
                DynamicParam {
                    if ($script:DoDynamicParam)
                    {
                        if ($PSVersionTable.PSVersion.Major -ge 3)
                        {
                            # -Parameters needs to be a PSBoundParametersDictionary object to work properly, due to internal
                            # details of the PS engine in v5.  Naturally, this is an internal type and we need to use reflection
                            # to make a new one.
    
                            $flags = [System.Reflection.BindingFlags]'Instance,NonPublic'
                            $params = $PSBoundParameters.GetType().GetConstructor($flags, $null, @(), $null).Invoke(@())
                        }
                        else
                        {
                            $params = @{}
                        }
    
                        $params['Path'] = [string[]]'Cert:\'
                        Get-MockDynamicParameters -CmdletName Get-ChildItem -Parameters $params
                    }
                }
    
                end
                {
                    'I am the original function'
                }
            }
        } | Import-Module -Force
    
        New-Module -Name TestModule2 {
            function CallingFunction
            {
                DynamicParamFunction -CodeSigningCert
            }
    
            function CallingFunction2 {
                [CmdletBinding()]
                param (
                    [ValidateScript({ [bool](DynamicParamFunction -CodeSigningCert) })]
                    [string]
                    $Whatever
                )
            }
        } | Import-Module -Force
    
        Mock DynamicParamFunction { if ($CodeSigningCert) { 'I am the mocked function' } } -ModuleName TestModule2
    
        It 'Properly evaluates dynamic parameters when called from another scope' {
            CallingFunction | Should Be 'I am the mocked function'
        }
    
        It 'Properly evaluates dynamic parameters when called from another scope when the call is from a ValidateScript block' {
            CallingFunction2 -Whatever 'Whatever'
        }
    
        AfterAll {
            Remove-Module TestModule1 -Force
            Remove-Module TestModule2 -Force
        }
    }
    
    Describe 'Parameter Filters and Common Parameters' {
        function Test-Function { [CmdletBinding()] param ( ) }
    
        Mock Test-Function { } -ParameterFilter { $VerbosePreference -eq 'Continue' }
    
        It 'Applies common parameters correctly when testing the parameter filter' {
            { Test-Function -Verbose } | Should Not Throw
            Assert-MockCalled Test-Function
            Assert-MockCalled Test-Function -ParameterFilter { $VerbosePreference -eq 'Continue' }
        }
    }
    
    Describe "Mocking Get-ItemProperty" {
        Mock Get-ItemProperty { New-Object -typename psobject -property @{ Name = "fakeName" } }
        It "Does not fail with NotImplementedException" {
            Get-ItemProperty -Path "HKLM:\Software\Key\" -Name "Property" | Select -ExpandProperty Name | Should Be fakeName
        }
    }
    
    Describe 'When mocking a command with parameters that match internal variable names' {
        function Test-Function
        {
            [CmdletBinding()]
            param (
                [string] $ArgumentList,
                [int] $FunctionName,
                [double] $ModuleName
            )
        }
    
        Mock Test-Function { return 'Mocked!' }
    
        It 'Should execute the mocked command successfully' {
            { Test-Function } | Should Not Throw
            Test-Function | Should Be 'Mocked!'
        }
    }
    
    Describe 'Mocking commands with potentially ambiguous parameter sets' {
        function SomeFunction
        {
            [CmdletBinding()]
            param
            (
                [parameter(ParameterSetName                = 'ps1',
                           ValueFromPipelineByPropertyName = $true)]
                [string]
                $p1,
    
                [parameter(ParameterSetName                = 'ps2',
                           ValueFromPipelineByPropertyName = $true)]
                [string]
                $p2
            )
            process
            {
                return $true
            }
        }
    
        Mock SomeFunction { }
    
        It 'Should call the function successfully, even with delayed parameter binding' {
            $object = New-Object psobject -Property @{ p1 = 'Whatever' }
            { $object | SomeFunction } | Should Not Throw
            Assert-MockCalled SomeFunction -ParameterFilter { $p1 -eq 'Whatever' }
        }
    }
    
    Describe 'When mocking a command that has an ArgumentList parameter with validation' {
        Mock Start-Process { return 'mocked' }
    
        It 'Calls the mock properly' {
            $hash = @{ Result = $null }
            $scriptBlock = { $hash.Result = Start-Process -FilePath cmd.exe -ArgumentList '/c dir c:\' }
    
            $scriptBlock | Should Not Throw
            $hash.Result | Should Be 'mocked'
        }
    }
    
    # These assertions won't actually "fail"; we had an infinite recursion bug in Get-DynamicParametersForCmdlet
    # if the caller mocked New-Object.  It should be fixed by making that call to New-Object module-qualified,
    # and this test will make sure it's working properly.  If this test fails, it'll take a really long time
    # to execute, and then will throw a stack overflow error.
    
    Describe 'Mocking New-Object' {
        It 'Works properly' {
            Mock New-Object
    
            $result = New-Object -TypeName Object
            $result | Should Be $null
            Assert-MockCalled New-Object
        }
    }
    
    Describe 'Mocking a function taking input from pipeline' {
        $psobj = New-Object -TypeName psobject -Property @{'PipeIntProp'='1';'PipeArrayProp'=1;'PipeStringProp'=1}
        $psArrayobj = New-Object -TypeName psobject -Property @{'PipeArrayProp'[email protected](1)}
        $noMockArrayResult = @(1,2) | PipelineInputFunction
        $noMockIntResult = 1 | PipelineInputFunction
        $noMockStringResult = '1' | PipelineInputFunction
        $noMockResultByProperty = $psobj | PipelineInputFunction -PipeStr 'val'
        $noMockArrayResultByProperty = $psArrayobj | PipelineInputFunction -PipeStr 'val'
    
        Mock PipelineInputFunction { write-output 'mocked' } -ParameterFilter { $PipeStr -eq 'blah' }
    
        context 'when calling original function with an array' {
            $result = @(1,2) | PipelineInputFunction
            it 'Returns actual implementation' {
                $result[0].keys | % {
                    $result[0][$_] | Should Be $noMockArrayResult[0][$_]
                    $result[1][$_] | Should Be $noMockArrayResult[1][$_]
                }
            }
        }
    
        context 'when calling original function with an int' {
            $result = 1 | PipelineInputFunction
            it 'Returns actual implementation' {
                $result.keys | % {
                    $result[$_] | Should Be $noMockIntResult[$_]
                }
            }
        }
    
        context 'when calling original function with a string' {
            $result = '1' | PipelineInputFunction
            it 'Returns actual implementation' {
                $result.keys | % {
                    $result[$_] | Should Be $noMockStringResult[$_]
                }
            }
        }
    
        context 'when calling original function and pipeline is bound by property name' {
            $result = $psobj | PipelineInputFunction -PipeStr 'val'
            it 'Returns actual implementation' {
                $result.keys | % {
                    $result[$_] | Should Be $noMockResultByProperty[$_]
                }
            }
        }
    
        context 'when calling original function and forcing a parameter binding exception' {
            Mock PipelineInputFunction {
                if($MyInvocation.ExpectingInput) {
                    throw New-Object -TypeName System.Management.Automation.ParameterBindingException
                }
                write-output $MyInvocation.ExpectingInput
            }
            $result = $psobj | PipelineInputFunction
    
            it 'falls back to no pipeline input' {
                $result | Should Be $false
            }
        }
    
        context 'when calling original function and pipeline is bound by property name with array values' {
            $result = $psArrayobj | PipelineInputFunction -PipeStr 'val'
            it 'Returns actual implementation' {
                $result.keys | % {
                    $result[$_] | Should Be $noMockArrayResultByProperty[$_]
                }
            }
        }
    
        context 'when calling the mocked function' {
            $result = 'blah' | PipelineInputFunction
            it 'Returns mocked implementation' {
                $result | Should Be 'mocked'
            }
        }
    }
    
    Describe 'Mocking module-qualified calls' {
        It 'Mock alias should not exist before the mock is defined' {
            $alias = Get-Alias -Name 'Microsoft.PowerShell.Management\Get-Content' -ErrorAction SilentlyContinue
            $alias | Should Be $null
        }
    
        $mockFile = 'TestDrive:\TestFile'
        $mockResult = 'Mocked'
    
        Mock Get-Content { return $mockResult } -ParameterFilter { $Path -eq $mockFile }
        Setup -File TestFile -Content 'The actual file'
    
        It 'Creates the alias while the mock is in effect' {
            $alias = Get-Alias -Name 'Microsoft.PowerShell.Management\Get-Content' -ErrorAction SilentlyContinue
            $alias | Should Not Be $null
        }
    
        It 'Calls the mock properly even if the call is module-qualified' {
            $result = Microsoft.PowerShell.Management\Get-Content -Path $mockFile
            $result | Should Be $mockResult
        }
    }
    
    Describe 'After a mock goes out of scope' {
        It 'Removes the alias after the mock goes out of scope' {
            $alias = Get-Alias -Name 'Microsoft.PowerShell.Management\Get-Content' -ErrorAction SilentlyContinue
            $alias | Should Be $null
        }
    }
    
    Describe 'Assert-MockCalled with Aliases' {
        AfterEach {
            if (Test-Path alias:PesterTF) { Remove-Item Alias:PesterTF }
        }
    
        It 'Allows calls to Assert-MockCalled to use both aliases and the original command name' {
            function TestFunction { }
            Set-Alias -Name PesterTF -Value TestFunction
            Mock PesterTF
            $null = PesterTF
    
            { Assert-MockCalled PesterTF } | Should Not Throw
            { Assert-MockCalled TestFunction } | Should Not Throw
        }
    }
    
    Describe 'Mocking Get-Command' {
        # This was reported as a bug in 3.3.12; we were relying on Get-Command to safely invoke other commands.
        # Mocking Get-Command, though, would result in infinite recursion.
    
        It 'Does not break when Get-Command is mocked' {
            { Mock Get-Command } | Should Not Throw
        }
    }
    
    Describe 'Mocks with closures' {
        $closureVariable = 'from closure'
        $scriptBlock = { "Variable resolved $closureVariable" }
        $closure = $scriptBlock.GetNewClosure()
        $closureVariable = 'from script'
    
        function TestClosure([switch] $Closure) { 'Not mocked' }
    
        Mock TestClosure $closure -ParameterFilter { $Closure }
        Mock TestClosure $scriptBlock
    
        It 'Resolves variables in the closure rather than Pester''s current scope' {
            TestClosure | Should Be 'Variable resolved from script'
            TestClosure -Closure | Should Be 'Variable resolved from closure'
        }
    }
    
    Describe '$args handling' {
    
        function AdvancedFunction {
            [CmdletBinding()]
            param()
            'orig'
        }
        function SimpleFunction {
            . AdvancedFunction
        }
        function AdvancedFunctionWithArgs {
            [CmdletBinding()]
            param($Args)
            'orig'
        }
        Add-Type -TypeDefinition '
            using System.Management.Automation;
            [Cmdlet(VerbsLifecycle.Invoke, "CmdletWithArgs")]
            public class InvokeCmdletWithArgs : Cmdlet {
                public InvokeCmdletWithArgs() { }
                [Parameter]
                public object Args {
                    set { }
                }
                protected override void EndProcessing() {
                    WriteObject("orig");
                }
            }
        ' -PassThru | Select-Object -ExpandProperty Assembly | Import-Module
    
        Mock AdvancedFunction { 'mock' }
        Mock AdvancedFunctionWithArgs { 'mock' }
        Mock Invoke-CmdletWithArgs { 'mock' }
    
        It 'Advanced function mock should be callable with dot operator' {
            SimpleFunction garbage | Should Be mock
        }
        It 'Advanced function with Args parameter should be mockable' {
            AdvancedFunctionWithArgs -Args garbage | Should Be mock
        }
        It 'Cmdlet with Args parameter should be mockable' {
            Invoke-CmdletWithArgs -Args garbage | Should Be mock
        }
    
    }
    
    Describe 'Single quote in command/module name' {
        BeforeAll {
            $module = New-Module "Module '‘’‚‛" {
                Function NormalCommandName { 'orig' }
                New-Item "Function::Command '‘’‚‛" -Value { 'orig' }
            } | Import-Module -PassThru
        }
    
        AfterAll {
            if ($module) { Remove-Module $module; $module = $null }
        }
    
        It 'Command with single quote in module name should be mockable' {
            Mock NormalCommandName { 'mock' }
            NormalCommandName | Should Be mock
        }
        It 'Command with single quote in name should be mockable' {
            Mock "Command '‘’‚‛" { 'mock' }
            & "Command '‘’‚‛" | Should Be mock
        }
    
    }
    
    if ($global:PSVersionTable.PSVersion.Major -ge 3) {
        Describe 'Mocking cmdlet without positional parameters' {
    
            Add-Type -TypeDefinition '
                using System.Management.Automation;
                [Cmdlet(VerbsLifecycle.Invoke, "CmdletWithoutPositionalParameters")]
                public class InvokeCmdletWithoutPositionalParameters : Cmdlet {
                    public InvokeCmdletWithoutPositionalParameters() { }
                    [Parameter]
                    public object Parameter {
                        set { }
                    }
                }
                [Cmdlet(VerbsLifecycle.Invoke, "CmdletWithValueFromRemainingArguments")]
                public class InvokeCmdletWithValueFromRemainingArguments : Cmdlet {
                    private string parameter;
                    private string[] remainings;
                    public InvokeCmdletWithValueFromRemainingArguments() { }
                    [Parameter]
                    public string Parameter {
                        set {
                            parameter=value;
                        }
                    }
                    [Parameter(ValueFromRemainingArguments=true)]
                    public string[] Remainings {
                        set {
                            remainings=value;
                        }
                    }
                    protected override void EndProcessing() {
                        WriteObject(string.Concat(parameter, "; ", string.Join(", ", remainings)));
                    }
                }
            ' -PassThru | Select-Object -First 1 -ExpandProperty Assembly | Import-Module
    
            It 'Original cmdlet does not have positional parameters' {
                { Invoke-CmdletWithoutPositionalParameters garbage } | Should Throw
            }
            Mock Invoke-CmdletWithoutPositionalParameters
            It 'Mock of cmdlet should not make parameters to be positional' {
                { Invoke-CmdletWithoutPositionalParameters garbage } | Should Throw
            }
    
            It 'Original cmdlet bind all to Remainings' {
                Invoke-CmdletWithValueFromRemainingArguments asd fgh jkl | Should Be '; asd, fgh, jkl'
            }
            Mock Invoke-CmdletWithValueFromRemainingArguments { -join ($Parameter, '; ', ($Remainings -join ', ')) }
            It 'Mock of cmdlet should bind all to Remainings' {
                Invoke-CmdletWithValueFromRemainingArguments asd fgh jkl | Should Be '; asd, fgh, jkl'
            }
    
        }
    }
    
    Describe 'Nested Mock calls' {
        $testDate = New-Object DateTime(2012,6,13)
    
        Mock Get-Date -ParameterFilter { $null -eq $Date } {
            Get-Date -Date $testDate -Format o
        }
    
        It 'Properly handles nested mocks' {
            $result = @(Get-Date)
            $result.Count | Should Be 1
            $result[0] | Should Be '2012-06-13T00:00:00.0000000'
        }
    }
    
    Describe 'Globbing characters in command name' {
    
        function f[f]f { 'orig1' }
        function f?f { 'orig2' }
        function f*f { 'orig3' }
        function fff { 'orig4' }
    
        It 'Command with globbing characters in name should be mockable' {
            Mock f[f]f { 'mock1' }
            Mock f?f { 'mock2' }
            Mock f*f { 'mock3' }
            f[f]f | Should Be mock1
            f?f | Should Be mock2
            f*f | Should Be mock3
            fff | Should Be orig4
        }
    
    }
    
    Describe 'Naming conflicts in mocked functions' {
        function Sample {
            param(
                [string]
                ${Metadata}
            )
        }
    
        function Wrapper {
            Sample -Metadata 'test'
        }
    
        Mock -CommandName Sample { 'mocked' }
    
        It 'Works with commands that contain variables named Metadata' {
            Wrapper | Should Be 'mocked'
        }
    }
    
  • tools\Functions\New-Fixture.ps1 Show
    function New-Fixture {
        <#
        .SYNOPSIS
        This function generates two scripts, one that defines a function
        and another one that contains its tests.
    
        .DESCRIPTION
        This function generates two scripts, one that defines a function
        and another one that contains its tests. The files are by default
        placed in the current directory and are called and populated as such:
    
    
        The script defining the function: .\Clean.ps1:
    
        function Clean {
    
        }
    
        The script containing the example test .\Clean.Tests.ps1:
    
        $here = Split-Path -Parent $MyInvocation.MyCommand.Path
        $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".")
        . "$here\$sut"
    
        Describe "Clean" {
    
            It "does something useful" {
                $false | Should Be $true
            }
        }
    
    
        .PARAMETER Name
        Defines the name of the function and the name of the test to be created.
    
        .PARAMETER Path
        Defines path where the test and the function should be created, you can use full or relative path.
        If the parameter is not specified the scripts are created in the current directory.
    
        .EXAMPLE
        New-Fixture -Name Clean
    
        Creates the scripts in the current directory.
    
        .EXAMPLE
        New-Fixture C:\Projects\Cleaner Clean
    
        Creates the scripts in the C:\Projects\Cleaner directory.
    
        .EXAMPLE
        New-Fixture Cleaner Clean
    
        Creates a new folder named Cleaner in the current directory and creates the scripts in it.
    
        .LINK
        Describe
        Context
        It
        about_Pester
        about_Should
        #>
    
        param (
            [String]$Path = $PWD,
            [Parameter(Mandatory=$true)]
            [String]$Name
        )
        #region File contents
        #keep this formatted as is. the format is output to the file as is, including indentation
        $scriptCode = "function $name {`r`n`r`n}"
    
        $testCode = '$here = Split-Path -Parent $MyInvocation.MyCommand.Path
    $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace ''\.Tests\.'', ''.''
    . "$here\$sut"
    
    Describe "#name#" {
        It "does something useful" {
            $true | Should Be $false
        }
    }' -replace "#name#",$name
    
        #endregion
    
        $Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
    
        Create-File -Path $Path -Name "$Name.ps1" -Content $scriptCode
        Create-File -Path $Path -Name "$Name.Tests.ps1" -Content $testCode
    }
    
    function Create-File ($Path,$Name,$Content) {
        if (-not (& $SafeCommands['Test-Path'] -Path $Path)) {
            & $SafeCommands['New-Item'] -ItemType Directory -Path $Path | & $SafeCommands['Out-Null']
        }
    
        $FullPath = & $SafeCommands['Join-Path'] -Path $Path -ChildPath $Name
        if (-not (& $SafeCommands['Test-Path'] -Path $FullPath)) {
            & $SafeCommands['Set-Content'] -Path  $FullPath -Value $Content -Encoding UTF8
            & $SafeCommands['Get-Item'] -Path $FullPath
        }
        else
        {
            # This is deliberately not sent through $SafeCommands, because our own tests rely on
            # mocking Write-Warning, and it's not really the end of the world if this call happens to
            # be screwed up in an edge case.
            Write-Warning "Skipping the file '$FullPath', because it already exists."
        }
    }
    
  • tools\Functions\New-Fixture.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    Describe "New-Fixture" {
        It "Name parameter is mandatory:" {
            (get-command New-Fixture ).Parameters.Name.ParameterSets.__AllParameterSets.IsMandatory | Should Be $true
        }
    
        Context "Only Name parameter is specified:" {
            It "Creates fixture in current directory:" {
                $name = "Test-Fixture"
                $path = "TestDrive:\"
    
                pushd  $path
                New-Fixture -Name $name | Out-Null
                popd
    
                Join-Path -Path $path -ChildPath "$name.ps1" | Should Exist
                Join-Path -Path $path -ChildPath "$name.Tests.ps1" | Should Exist
            }
        }
    
        Context "Name and Path parameter is specified:" {
            #use different fixture names to avoid interference among the test cases
            #cleaning up would be also possible, but difficult if the assertion fails
            It "Creates fixture in full Path:" {
                $name = "Test-Fixture"
                $path = "TestDrive:\full"
    
                New-Fixture -Name $name -Path $path | Out-Null
    
                Join-Path -Path $path -ChildPath "$name.ps1" | Should Exist
                Join-Path -Path $path -ChildPath "$name.Tests.ps1" | Should Exist
    
                #cleanup
                Join-Path -Path "$path" -ChildPath "$name.ps1" | Remove-Item -Force
                Join-Path -Path "$path" -ChildPath "$name.Tests.ps1" | Remove-Item -Force
            }
    
            It "Creates fixture in relative Path:" {
                $name = "Relative1-Fixture"
                $path = "TestDrive:\"
    
                pushd  $path
                New-Fixture -Name $name -Path relative | Out-Null
                popd
    
                Join-Path -Path "$path\relative" -ChildPath "$name.ps1" | Should Exist
                Join-Path -Path "$path\relative" -ChildPath "$name.Tests.ps1" | Should Exist
            }
            It "Creates fixture if Path is set to '.':" {
                $name = "Relative2-Fixture"
                $path = "TestDrive:\"
    
                pushd  $path
                New-Fixture -Name $name -Path . | Out-Null
                popd
    
                Join-Path -Path "$path" -ChildPath "$name.ps1" | Should Exist
                Join-Path -Path "$path" -ChildPath "$name.Tests.ps1" | Should Exist
            }
            It "Creates fixture if Path is set to '(pwd)':" {
                $name = "Relative3-Fixture"
                $path = "TestDrive:\"
    
                pushd  $path
                New-Fixture -Name $name -Path (pwd) | Out-Null
                popd
    
                Join-Path -Path "$path" -ChildPath "$name.ps1" | Should Exist
                Join-Path -Path "$path" -ChildPath "$name.Tests.ps1" | Should Exist
            }
            It "Writes warning if file exists" {
                $name = "Warning-Fixture"
                $path = "TestDrive:\"
    
                Mock -Verifiable -ModuleName Pester Write-Warning { }
    
                #Create the same files twice
                New-Fixture -Name $name -Path $path | Out-Null
                New-Fixture -Name $name -Path $path -WarningVariable warnings -WarningAction SilentlyContinue | Out-Null
    
                Assert-VerifiableMocks
            }
    
        }
        #TODO add tests that validate the contents of default files
    }
    
    
  • tools\Functions\New-MockObject.ps1 Show
    function New-MockObject {
        <#
        .SYNOPSIS
            This function instantiates a .NET object from a type. The assembly for the particular type must be
            loaded.
    
        .PARAMETER Type
            The .NET type to create an object from.
    
        .EXAMPLE
            PS> $obj = New-MockObject -Type 'System.Diagnostics.Process'
            PS> $obj.GetType().FullName
                System.Diagnostics.Process
        #>
    
        param (
            [Parameter(Mandatory = $true)]
            [ValidateNotNullOrEmpty()]
            [type]$Type
        )
    
        [System.Runtime.Serialization.Formatterservices]::GetUninitializedObject($Type)
    
    }
    
  • tools\Functions\New-MockObject.Tests.ps1 Show
    describe 'New-MockObject' {
    
        it 'instantiates an object from a class with no public constructors' {
            $type = 'Microsoft.PowerShell.Commands.Language'
            New-MockObject -Type $type | should beoftype $type
        }
    }
    
  • tools\Functions\Output.ps1 Show
    $Script:ReportStrings = DATA {
        @{
            StartMessage   = 'Executing all tests in {0}'
            FilterMessage  = ' matching test name {0}'
            TagMessage     = ' with Tags {0}'
            MessageOfs     = "', '"
    
            CoverageTitle   = 'Code coverage report:'
            CoverageMessage = 'Covered {2:P2} of {3:N0} analyzed {0} in {4:N0} {1}.'
            MissedSingular  = 'Missed command:'
            MissedPlural    = 'Missed commands:'
            CommandSingular = 'Command'
            CommandPlural   = 'Commands'
            FileSingular    = 'File'
            FilePlural      = 'Files'
    
            Describe = 'Describing {0}'
            Script   = 'Executing script {0}'
            Context  = 'Context {0}'
            Margin   = '  '
            Timing   = 'Tests completed in {0}'
    
            # If this is set to an empty string, the count won't be printed
            ContextsPassed = ''
            ContextsFailed = ''
    
            TestsPassed       = 'Tests Passed: {0}, '
            TestsFailed       = 'Failed: {0}, '
            TestsSkipped      = 'Skipped: {0}, '
            TestsPending      = 'Pending: {0}, '
            TestsInconclusive = 'Inconclusive: {0} '
        }
    }
    
    $Script:ReportTheme = DATA {
        @{
            Describe       = 'Green'
            DescribeDetail = 'DarkYellow'
            Context        = 'Cyan'
            ContextDetail  = 'DarkCyan'
            Pass           = 'DarkGreen'
            PassTime       = 'DarkGray'
            Fail           = 'Red'
            FailTime       = 'DarkGray'
            Skipped        = 'Yellow'
            Pending        = 'Gray'
            Inconclusive   = 'Gray'
            Incomplete     = 'Yellow'
            IncompleteTime = 'DarkGray'
            Foreground     = 'White'
            Information    = 'DarkGray'
            Coverage       = 'White'
            CoverageWarn   = 'DarkRed'
        }
    }
    
    function Write-PesterStart {
        param(
            [Parameter(mandatory=$true, valueFromPipeline=$true)]
            $PesterState,
            $Path = $Path
        )
        process {
            if(-not ( $pester.Show | Has-Flag Header)) { return }
    
            $OFS = $ReportStrings.MessageOfs
    
            $message = $ReportStrings.StartMessage -f "$($Path)"
            if ($PesterState.TestNameFilter) {
               $message += $ReportStrings.FilterMessage -f "$($PesterState.TestNameFilter)"
            }
            if ($PesterState.TagFilter) {
               $message += $ReportStrings.TagMessage -f "$($PesterState.TagFilter)"
            }
    
            & $SafeCommands['Write-Host'] $message -Foreground $ReportTheme.Foreground
        }
    }
    
    function Write-Describe {
        param (
            [Parameter(mandatory=$true, valueFromPipeline=$true)]
            $Describe,
    
            [string] $CommandUsed = 'Describe'
        )
        process {
            if(-not ( $pester.Show | Has-Flag Describe)) { return }
    
            $margin = $ReportStrings.Margin * $pester.IndentLevel
    
            $Text = if($Describe.PSObject.Properties['Name'] -and $Describe.Name) {
                $ReportStrings.$CommandUsed -f $Describe.Name
            } else {
                $ReportStrings.$CommandUsed -f $Describe
            }
    
            & $SafeCommands['Write-Host']
            & $SafeCommands['Write-Host'] "${margin}${Text}" -ForegroundColor $ReportTheme.Describe
            # If the feature has a longer description, write that too
            if($Describe.PSObject.Properties['Description'] -and $Describe.Description) {
                $Describe.Description -split '\n' | % {
                    & $SafeCommands['Write-Host'] ($ReportStrings.Margin * ($pester.IndentLevel + 1)) $_ -ForegroundColor $ReportTheme.DescribeDetail
                }
            }
        }
    }
    
    function Write-Context {
        param (
            [Parameter(mandatory=$true, valueFromPipeline=$true)]
            $Context
        )
        process {
            if(-not ( $pester.Show | Has-Flag Context)) { return }
            $Text = if($Context.PSObject.Properties['Name'] -and $Context.Name) {
                    $ReportStrings.Context -f $Context.Name
                } else {
                    $ReportStrings.Context -f $Context
                }
    
            & $SafeCommands['Write-Host']
            & $SafeCommands['Write-Host'] ($ReportStrings.Margin + $Text) -ForegroundColor $ReportTheme.Context
            # If the scenario has a longer description, write that too
            if($Context.PSObject.Properties['Description'] -and $Context.Description) {
                $Context.Description -split '\n' | % {
                    & $SafeCommands['Write-Host'] (" " * $ReportStrings.Context.Length) $_ -ForegroundColor $ReportTheme.ContextDetail
                }
            }
        }
    }
    
    function ConvertTo-FailureLines
    {
        param (
            [Parameter(mandatory=$true, valueFromPipeline=$true)]
            $ErrorRecord
        )
        process {
            $lines = @{
                Message = @()
                Trace = @()
            }
    
            ## convert the exception messages
            $exception = $ErrorRecord.Exception
            $exceptionLines = @()
            while ($exception)
            {
                $exceptionName = $exception.GetType().Name
                $thisLines = $exception.Message.Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries)
                if ($ErrorRecord.FullyQualifiedErrorId -ne 'PesterAssertionFailed')
                {
                    $thisLines[0] = "$exceptionName`: $($thisLines[0])"
                }
                [array]::Reverse($thisLines)
                $exceptionLines += $thisLines
                $exception = $exception.InnerException
            }
            [array]::Reverse($exceptionLines)
            $lines.Message += $exceptionLines
            if ($ErrorRecord.FullyQualifiedErrorId -eq 'PesterAssertionFailed')
            {
                $lines.Message += "$($ErrorRecord.TargetObject.Line)`: $($ErrorRecord.TargetObject.LineText)".Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries)
            }
    
            if ( -not ($ErrorRecord | & $SafeCommands['Get-Member'] -Name ScriptStackTrace) )
            {
                if ($ErrorRecord.FullyQualifiedErrorID -eq 'PesterAssertionFailed')
                {
                    $lines.Trace += "at line: $($ErrorRecord.TargetObject.Line) in $($ErrorRecord.TargetObject.File)"
                }
                else
                {
                    $lines.Trace += "at line: $($ErrorRecord.InvocationInfo.ScriptLineNumber) in $($ErrorRecord.InvocationInfo.ScriptName)"
                }
                return $lines
            }
    
            ## convert the stack trace
            $traceLines = $ErrorRecord.ScriptStackTrace.Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries)
    
            # omit the lines internal to Pester
            $count = 0
            foreach ( $line in $traceLines )
            {
                if ( $line -match '^at (Invoke-Test|Context(?:Impl)?|Describe(?:Impl)?|InModuleScope|Invoke-Pester), .*\\Functions\\.*.ps1: line [0-9]*$' )
                {
                    break
                }
                $count ++
            }
            $lines.Trace += $traceLines |
                & $SafeCommands['Select-Object'] -First $count |
                & $SafeCommands['Where-Object'] {
                    $_ -notmatch '^at (?:Should<End>|Invoke-(?:Legacy)?Assertion), .*\\Functions\\Assertions\\Should.ps1: line [0-9]*$' -and
                    $_ -notmatch '^at Assert-MockCalled, .*\\Functions\\Mock.ps1: line [0-9]*$'
                }
    
            return $lines
        }
    }
    
    function Write-PesterResult {
        param (
            [Parameter(mandatory=$true, valueFromPipeline=$true)]
            $TestResult
        )
    
        process {
            $quiet = $pester.Show -eq [Pester.OutputTypes]::None
            $OutputType = [Pester.OutputTypes] $TestResult.Result
            $writeToScreen = $pester.Show | Has-Flag $OutputType
            $skipOutput = $quiet -or (-not $writeToScreen)
    
            if ($skipOutput)
            {
                return
            }
    
            $margin = $ReportStrings.Margin * ($pester.IndentLevel + 1)
            $error_margin = $margin + $ReportStrings.Margin
            $output = $TestResult.name
            $humanTime = Get-HumanTime $TestResult.Time.TotalSeconds
    
            if (-not ($OutputType | Has-Flag 'Default, Summary'))
            {
                switch ($TestResult.Result)
                {
                    Passed {
                        & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Pass "$margin[+] $output " -NoNewLine
                        & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.PassTime $humanTime
                        break
            	    }
    
                    Failed {
                        & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Fail "$margin[-] $output " -NoNewLine
                        & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.FailTime $humanTime
                        & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Fail $($TestResult.failureMessage -replace '(?m)^',$error_margin)
                        & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Fail $($TestResult.stackTrace -replace '(?m)^',$error_margin)
                        break
    	            }
    
                    Skipped {
                        & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Skipped "$margin[!] $output $humanTime"
                        break
                    }
    
                    Pending {
                        & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Pending "$margin[?] $output $humanTime"
                        break
                    }
    
                    Inconclusive {
                        & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Inconclusive "$margin[?] $output $humanTime"
    
                        if ($testresult.FailureMessage) {
                            & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Inconclusive $($TestResult.failureMessage -replace '(?m)^',$error_margin)
                        }
    
                        & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Inconclusive $($TestResult.stackTrace -replace '(?m)^',$error_margin)
                        break
                    }
    
                    default {
                        # TODO:  Add actual Incomplete status as default rather than checking for null time.
                        if($null -eq $TestResult.Time) {
                            & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.Incomplete "$margin[?] $output " -NoNewLine
                            & $SafeCommands['Write-Host'] -ForegroundColor $ReportTheme.IncompleteTime $humanTime
                        }
                    }
                }
            }
        }
    }
    
    function Write-PesterReport {
        param (
            [Parameter(mandatory=$true, valueFromPipeline=$true)]
            $PesterState
        )
        if(-not ($PesterState.Show | Has-Flag Summary)) { return }
    
        & $SafeCommands['Write-Host'] ($ReportStrings.Timing -f (Get-HumanTime $PesterState.Time.TotalSeconds)) -Foreground $ReportTheme.Foreground
    
        $Success, $Failure = if($PesterState.FailedCount -gt 0) {
                                $ReportTheme.Foreground, $ReportTheme.Fail
                             } else {
                                $ReportTheme.Pass, $ReportTheme.Information
                             }
        $Skipped = if($PesterState.SkippedCount -gt 0) { $ReportTheme.Skipped } else { $ReportTheme.Information }
        $Pending = if($PesterState.PendingCount -gt 0) { $ReportTheme.Pending } else { $ReportTheme.Information }
        $Inconclusive = if($PesterState.InconclusiveCount -gt 0) { $ReportTheme.Inconclusive } else { $ReportTheme.Information }
    
        if($ReportStrings.ContextsPassed) {
            & $SafeCommands['Write-Host'] ($ReportStrings.ContextsPassed -f $PesterState.PassedScenarios.Count) -Foreground $Success -NoNewLine
            & $SafeCommands['Write-Host'] ($ReportStrings.ContextsFailed -f $PesterState.FailedScenarios.Count) -Foreground $Failure
        }
        if($ReportStrings.TestsPassed) {
            & $SafeCommands['Write-Host'] ($ReportStrings.TestsPassed -f $PesterState.PassedCount) -Foreground $Success -NoNewLine
            & $SafeCommands['Write-Host'] ($ReportStrings.TestsFailed -f $PesterState.FailedCount) -Foreground $Failure -NoNewLine
            & $SafeCommands['Write-Host'] ($ReportStrings.TestsSkipped -f $PesterState.SkippedCount) -Foreground $Skipped -NoNewLine
            & $SafeCommands['Write-Host'] ($ReportStrings.TestsPending -f $PesterState.PendingCount) -Foreground $Pending -NoNewLine
            & $SafeCommands['Write-Host'] ($ReportStrings.TestsInconclusive -f $PesterState.InconclusiveCount) -Foreground $Pending
        }
    }
    
    function Write-CoverageReport {
        param ([object] $CoverageReport)
    
        if ($null -eq $CoverageReport -or ($pester.Show -eq [Pester.OutputTypes]::None) -or $CoverageReport.NumberOfCommandsAnalyzed -eq 0)
        {
            return
        }
    
        $totalCommandCount = $CoverageReport.NumberOfCommandsAnalyzed
        $fileCount = $CoverageReport.NumberOfFilesAnalyzed
        $executedPercent = ($CoverageReport.NumberOfCommandsExecuted / $CoverageReport.NumberOfCommandsAnalyzed).ToString("P2")
    
        $command = if ($totalCommandCount -gt 1) { $ReportStrings.CommandPlural } else { $ReportStrings.CommandSingular }
        $file = if ($fileCount -gt 1) { $ReportStrings.FilePlural } else { $ReportStrings.FileSingular }
    
        $commonParent = Get-CommonParentPath -Path $CoverageReport.AnalyzedFiles
        $report = $CoverageReport.MissedCommands | & $SafeCommands['Select-Object'] -Property @(
            @{ Name = 'File'; Expression = { Get-RelativePath -Path $_.File -RelativeTo $commonParent } }
            'Function'
            'Line'
            'Command'
        )
    
        & $SafeCommands['Write-Host']
        & $SafeCommands['Write-Host'] $ReportStrings.CoverageTitle -Foreground $ReportTheme.Coverage
    
        if ($CoverageReport.MissedCommands.Count -gt 0)
        {
            & $SafeCommands['Write-Host'] ($ReportStrings.CoverageMessage -f $command, $file, $executedPercent, $totalCommandCount, $fileCount) -Foreground $ReportTheme.CoverageWarn
            if ($CoverageReport.MissedCommands.Count -eq 1)
            {
                & $SafeCommands['Write-Host'] $ReportStrings.MissedSingular -Foreground $ReportTheme.CoverageWarn
            } else {
                & $SafeCommands['Write-Host'] $ReportStrings.MissedPlural -Foreground $ReportTheme.CoverageWarn
            }
            $report | & $SafeCommands['Format-Table'] -AutoSize | & $SafeCommands['Out-Host']
        } else {
            & $SafeCommands['Write-Host'] ($ReportStrings.CoverageMessage -f $command, $file, $executedPercent, $totalCommandCount, $fileCount) -Foreground $ReportTheme.Coverage
        }
    }
    
  • tools\Functions\Output.Tests.ps1 Show
    InModuleScope -ModuleName Pester -ScriptBlock {
        Describe 'Has-Flag' -Fixture {
            It 'Returns true when setting and value are the same' {
                $setting = [Pester.OutputTypes]::Passed
                $value = [Pester.OutputTypes]::Passed
    
                $value | Has-Flag $setting | Should Be $true
            }
    
            It 'Returns false when setting and value are the different' {
                $setting = [Pester.OutputTypes]::Passed
                $value = [Pester.OutputTypes]::Failed
    
                $value | Has-Flag $setting | Should Be $false
            }
    
            It 'Returns true when setting contains value' {
                $setting = [Pester.OutputTypes]::Passed -bor [Pester.OutputTypes]::Failed
                $value = [Pester.OutputTypes]::Passed
    
                $value | Has-Flag $setting | Should Be $true
            }
    
            It 'Returns false when setting does not contain the value' {
                $setting = [Pester.OutputTypes]::Passed -bor [Pester.OutputTypes]::Failed
                $value = [Pester.OutputTypes]::Summary
    
                $value | Has-Flag $setting | Should Be $false
            }
    
            It 'Returns true when at least one setting is contained in value' {
                $setting = [Pester.OutputTypes]::Passed -bor [Pester.OutputTypes]::Failed
                $value = [Pester.OutputTypes]::Summary -bor [Pester.OutputTypes]::Failed
    
                $value | Has-Flag $setting | Should Be $true
            }
    
            It 'Returns false when none of settings is contained in value' {
                $setting = [Pester.OutputTypes]::Passed -bor [Pester.OutputTypes]::Failed
                $value = [Pester.OutputTypes]::Summary -bor [Pester.OutputTypes]::Describe
    
                $value | Has-Flag $setting | Should Be $false
            }
        }
    
        Describe 'Default OutputTypes' -Fixture {
            It 'Fails output type contains all except passed' {
                $expected = [Pester.OutputTypes]'Default, Failed, Pending, Skipped, Inconclusive, Describe, Context, Summary, Header'
                [Pester.OutputTypes]::Fails | Should Be $expected
            }
    
            It 'All output type contains all flags' {
                $expected = [Pester.OutputTypes]'Default, Passed, Failed, Pending, Skipped, Inconclusive, Describe, Context, Summary, Header'
                [Pester.OutputTypes]::All | Should Be $expected
            }
        }
    }
    
  • tools\Functions\PesterState.ps1 Show
    function New-PesterState
    {
        param (
            [String[]]$TagFilter,
            [String[]]$ExcludeTagFilter,
            [String[]]$TestNameFilter,
            [System.Management.Automation.SessionState]$SessionState,
            [Switch]$Strict,
            [Pester.OutputTypes]$Show = 'All',
            [object]$PesterOption
        )
    
        if ($null -eq $SessionState) { $SessionState = $ExecutionContext.SessionState }
    
        if ($null -eq $PesterOption)
        {
            $PesterOption = New-PesterOption
        }
        elseif ($PesterOption -is [System.Collections.IDictionary])
        {
            try
            {
                $PesterOption = New-PesterOption @PesterOption
            }
            catch
            {
                throw
            }
        }
    
        & $SafeCommands['New-Module'] -Name Pester -AsCustomObject -ArgumentList $TagFilter, $ExcludeTagFilter, $TestNameFilter, $SessionState, $Strict, $Show, $PesterOption -ScriptBlock {
            param (
                [String[]]$_tagFilter,
                [String[]]$_excludeTagFilter,
                [String[]]$_testNameFilter,
                [System.Management.Automation.SessionState]$_sessionState,
                [Switch]$Strict,
                [Pester.OutputTypes]$Show,
                [object]$PesterOption
            )
    
            #public read-only
            $TagFilter = $_tagFilter
            $ExcludeTagFilter = $_excludeTagFilter
            $TestNameFilter = $_testNameFilter
    
            $script:SessionState = $_sessionState
            $script:Stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
            $script:MostRecentTimestamp = 0
            $script:CommandCoverage = @()
            $script:Strict = $Strict
            $script:Show = $Show
    
            $script:TestResult = @()
    
            $script:TotalCount = 0
            $script:Time = [timespan]0
            $script:PassedCount = 0
            $script:FailedCount = 0
            $script:SkippedCount = 0
            $script:PendingCount = 0
            $script:InconclusiveCount = 0
    
            $script:IncludeVSCodeMarker = $PesterOption.IncludeVSCodeMarker
            $script:TestSuiteName       = $PesterOption.TestSuiteName
    
            $script:SafeCommands = @{}
    
            $script:SafeCommands['New-Object']          = & (Pester\SafeGetCommand) -Name New-Object          -Module Microsoft.PowerShell.Utility -CommandType Cmdlet
            $script:SafeCommands['Select-Object']       = & (Pester\SafeGetCommand) -Name Select-Object       -Module Microsoft.PowerShell.Utility -CommandType Cmdlet
            $script:SafeCommands['Export-ModuleMember'] = & (Pester\SafeGetCommand) -Name Export-ModuleMember -Module Microsoft.PowerShell.Core    -CommandType Cmdlet
            $script:SafeCommands['Add-Member']          = & (Pester\SafeGetCommand) -Name Add-Member          -Module Microsoft.PowerShell.Utility -CommandType Cmdlet
    
            function New-TestGroup([string] $Name, [string] $Hint)
            {
                & $SafeCommands['New-Object'] psobject -Property @{
                    Name              = $Name
                    Type              = 'TestGroup'
                    Hint              = $Hint
                    Actions           = [System.Collections.ArrayList]@()
                    BeforeEach        = & $SafeCommands['New-Object'] System.Collections.Generic.List[scriptblock]
                    AfterEach         = & $SafeCommands['New-Object'] System.Collections.Generic.List[scriptblock]
                    BeforeAll         = & $SafeCommands['New-Object'] System.Collections.Generic.List[scriptblock]
                    AfterAll          = & $SafeCommands['New-Object'] System.Collections.Generic.List[scriptblock]
                    TotalCount        = 0
                    Time              = [timespan]0
                    PassedCount       = 0
                    FailedCount       = 0
                    SkippedCount      = 0
                    PendingCount      = 0
                    InconclusiveCount = 0
                }
            }
    
            $script:TestActions = New-TestGroup -Name Pester -Hint Root
            $script:TestGroupStack = & $SafeCommands['New-Object'] System.Collections.Stack
            $script:TestGroupStack.Push($script:TestActions)
    
            function EnterTestGroup([string] $Name, [string] $Hint)
            {
                $newGroup = New-TestGroup @PSBoundParameters
                $null = $script:TestGroupStack.Peek().Actions.Add($newGroup)
                $script:TestGroupStack.Push($newGroup)
            }
    
            function LeaveTestGroup([string] $Name, [string] $Hint)
            {
                $currentGroup = $script:TestGroupStack.Pop()
    
                if ($currentGroup.Name -ne $Name -or $currentGroup.Hint -ne $Hint)
                {
                    throw "TestGroups stack corrupted:  Expected name/hint of '$Name','$Hint'.  Found '$($currentGroup.Name)', '$($currentGroup.Hint)'."
                }
            }
    
            function AddTestResult
            {
                param (
                    [string]$Name,
                    [ValidateSet("Failed","Passed","Skipped","Pending","Inconclusive")]
                    [string]$Result,
                    [Nullable[TimeSpan]]$Time,
                    [string]$FailureMessage,
                    [string]$StackTrace,
                    [string] $ParameterizedSuiteName,
                    [System.Collections.IDictionary] $Parameters,
                    [System.Management.Automation.ErrorRecord] $ErrorRecord
                )
    
                $previousTime = $script:MostRecentTimestamp
                $script:MostRecentTimestamp = $script:Stopwatch.Elapsed
    
                if ($null -eq $Time)
                {
                    $Time = $script:MostRecentTimestamp - $previousTime
                }
    
                if (-not $script:Strict)
                {
                    $Passed = "Passed","Skipped","Pending" -contains $Result
                }
                else
                {
                    $Passed = $Result -eq "Passed"
                    if (($Result -eq "Skipped") -or ($Result -eq "Pending"))
                    {
                        $FailureMessage = "The test failed because the test was executed in Strict mode and the result '$result' was translated to Failed."
                        $Result = "Failed"
                    }
    
                }
    
                $script:TotalCount++
                $script:Time += $Time
    
                switch ($Result)
                {
                    Passed  { $script:PassedCount++; break; }
                    Failed  { $script:FailedCount++; break; }
                    Skipped { $script:SkippedCount++; break; }
                    Pending { $script:PendingCount++; break; }
                    Inconclusive { $script:InconclusiveCount++; break; }
                }
    
                $resultRecord = & $SafeCommands['New-Object'] -TypeName PsObject -Property @{
                    Name                   = $Name
                    Type                   = 'TestCase'
                    Passed                 = $Passed
                    Result                 = $Result
                    Time                   = $Time
                    FailureMessage         = $FailureMessage
                    StackTrace             = $StackTrace
                    ErrorRecord            = $ErrorRecord
                    ParameterizedSuiteName = $ParameterizedSuiteName
                    Parameters             = $Parameters
                    Show                   = $script:Show
                }
    
                $null = $script:TestGroupStack.Peek().Actions.Add($resultRecord)
    
                # Attempting some degree of backward compatibility for the TestResult collection for now; deprecated and will be removed in the future
                $describe = ''
                $contexts = [System.Collections.ArrayList]@()
    
                foreach ($group in $script:TestGroupStack.GetEnumerator())
                {
                    if ($group.Hint -eq 'Root' -or $group.Hint -eq 'Script') { continue }
                    if ($describe -eq '')
                    {
                        $describe = $group.Name
                    }
                    else
                    {
                        $null = $contexts.Add($group.Name)
                    }
                }
    
                $context = $contexts -join '/'
    
                $script:TestResult += & $SafeCommands['New-Object'] psobject -Property @{
                    Describe               = $describe
                    Context                = $context
                    Name                   = $Name
                    Passed                 = $Passed
                    Result                 = $Result
                    Time                   = $Time
                    FailureMessage         = $FailureMessage
                    StackTrace             = $StackTrace
                    ErrorRecord            = $ErrorRecord
                    ParameterizedSuiteName = $ParameterizedSuiteName
                    Parameters             = $Parameters
                    Show                  = $script:Show
                }
            }
    
            function AddSetupOrTeardownBlock([scriptblock] $ScriptBlock, [string] $CommandName)
            {
                $currentGroup = $script:TestGroupStack.Peek()
    
                $isSetupCommand = IsSetupCommand -CommandName $CommandName
                $isGroupCommand = IsTestGroupCommand -CommandName $CommandName
    
                if ($isSetupCommand)
                {
                    if ($isGroupCommand)
                    {
                        $currentGroup.BeforeAll.Add($ScriptBlock)
                    }
                    else
                    {
                        $currentGroup.BeforeEach.Add($ScriptBlock)
                    }
                }
                else
                {
                    if ($isGroupCommand)
                    {
                        $currentGroup.AfterAll.Add($ScriptBlock)
                    }
                    else
                    {
                        $currentGroup.AfterEach.Add($ScriptBlock)
                    }
                }
            }
    
            function IsSetupCommand
            {
                param ([string] $CommandName)
                return $CommandName -eq 'BeforeEach' -or $CommandName -eq 'BeforeAll'
            }
    
            function IsTestGroupCommand
            {
                param ([string] $CommandName)
                return $CommandName -eq 'BeforeAll' -or $CommandName -eq 'AfterAll'
            }
    
            function GetTestCaseSetupBlocks
            {
                $blocks = @(
                    foreach ($group in $this.TestGroups)
                    {
                        $group.BeforeEach
                    }
                )
    
                return $blocks
            }
    
            function GetTestCaseTeardownBlocks
            {
                $groups = @($this.TestGroups)
                [Array]::Reverse($groups)
    
                $blocks = @(
                    foreach ($group in $groups)
                    {
                        $group.AfterEach
                    }
                )
    
                return $blocks
            }
    
            function GetCurrentTestGroupSetupBlocks
            {
                return $script:TestGroupStack.Peek().BeforeAll
            }
    
            function GetCurrentTestGroupTeardownBlocks
            {
                return $script:TestGroupStack.Peek().AfterAll
            }
    
            $ExportedVariables = "TagFilter",
            "ExcludeTagFilter",
            "TestNameFilter",
            "TestResult",
            "SessionState",
            "CommandCoverage",
            "Strict",
            "Show",
            "Time",
            "TotalCount",
            "PassedCount",
            "FailedCount",
            "SkippedCount",
            "PendingCount",
            "InconclusiveCount",
            "IncludeVSCodeMarker",
            "TestActions",
            "TestGroupStack",
            "TestSuiteName"
    
            $ExportedFunctions = "EnterTestGroup",
                                 "LeaveTestGroup",
                                 "AddTestResult",
                                 "AddSetupOrTeardownBlock",
                                 "GetTestCaseSetupBlocks",
                                 "GetTestCaseTeardownBlocks",
                                 "GetCurrentTestGroupSetupBlocks",
                                 "GetCurrentTestGroupTeardownBlocks"
    
            & $SafeCommands['Export-ModuleMember'] -Variable $ExportedVariables -function $ExportedFunctions
        }  |
        & $SafeCommands['Add-Member'] -PassThru -MemberType ScriptProperty -Name CurrentTestGroup -Value {
            $this.TestGroupStack.Peek()
        } |
        & $SafeCommands['Add-Member'] -PassThru -MemberType ScriptProperty -Name TestGroups -Value {
            $array = $this.TestGroupStack.ToArray()
            [Array]::Reverse($array)
            return $array
        } |
        & $SafeCommands['Add-Member'] -PassThru -MemberType ScriptProperty -Name IndentLevel -Value {
            # We ignore the root node of the stack here, and don't start indenting until after the Script nodes inside the root
            return [Math]::Max(0, $this.TestGroupStack.Count - 2)
        }
    }
    
  • tools\Functions\PesterState.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "New-PesterState" {
            Context "TestNameFilter parameter is set" {
                $p = new-pesterstate -TestNameFilter "filter"
    
                it "sets the TestNameFilter property" {
                    $p.TestNameFilter | should be "filter"
                }
    
            }
            Context "TagFilter parameter is set" {
                $p = new-pesterstate -TagFilter "tag","tag2"
    
                it "sets the TestNameFilter property" {
                    $p.TagFilter | should be ("tag","tag2")
                }
            }
    
            Context "ExcludeTagFilter parameter is set" {
                $p = new-pesterstate -ExcludeTagFilter "tag3", "tag"
    
                it "sets the ExcludeTagFilter property" {
                    $p.ExcludeTagFilter | should be ("tag3", "tag")
                }
            }
    
            Context "TagFilter and ExcludeTagFilter parameter are set" {
                $p = new-pesterstate -TagFilter "tag","tag2" -ExcludeTagFilter "tag3"
    
                it "sets the TestNameFilter property" {
                    $p.TagFilter | should be ("tag","tag2")
                }
    
                it "sets the ExcludeTagFilter property" {
                    $p.ExcludeTagFilter | should be ("tag3")
                }
            }
            Context "TestNameFilter and TagFilter parameter is set" {
                $p = new-pesterstate -TagFilter "tag","tag2" -testnamefilter "filter"
    
                it "sets the TestNameFilter property" {
                    $p.TagFilter | should be ("tag","tag2")
                }
    
                it "sets the TestNameFilter property" {
                    $p.TagFilter | should be ("tag","tag2")
                }
    
            }
        }
    
        Describe "Pester state object" {
            $p = New-PesterState
    
            Context "entering describe" {
                It "enters describe" {
                    $p.EnterTestGroup("describeName", "describe")
                    $p.CurrentTestGroup.Name | Should Be "describeName"
                    $p.CurrentTestGroup.Hint | Should Be "describe"
                }
            }
            Context "leaving describe" {
                It "leaves describe" {
                    $p.LeaveTestGroup("describeName", "describe")
                    $p.CurrentTestGroup.Name | Should Not Be "describeName"
                    $p.CurrentTestGroup.Hint | Should Not Be "describe"
                }
            }
    
            context "adding test result" {
                $p.EnterTestGroup('Describe', 'Describe')
    
                it "adds passed test" {
                    $p.AddTestResult("result","Passed", 100)
                    $result = $p.TestResult[-1]
                    $result.Name | should be "result"
                    $result.passed | should be $true
                    $result.Result | Should be "Passed"
                    $result.time.ticks | should be 100
                }
                it "adds failed test" {
                    try { throw 'message' } catch { $e = $_ }
                    $p.AddTestResult("result","Failed", 100, "fail", "stack","suite name",@{param='eters'},$e)
                    $result = $p.TestResult[-1]
                    $result.Name | should be "result"
                    $result.passed | should be $false
                    $result.Result | Should be "Failed"
                    $result.time.ticks | should be 100
                    $result.FailureMessage | should be "fail"
                    $result.StackTrace | should be "stack"
                    $result.ParameterizedSuiteName | should be "suite name"
                    $result.Parameters['param'] | should be 'eters'
                    $result.ErrorRecord.Exception.Message | should be 'message'
                }
    
                it "adds skipped test" {
                    $p.AddTestResult("result","Skipped", 100)
                    $result = $p.TestResult[-1]
                    $result.Name | should be "result"
                    $result.passed | should be $true
                    $result.Result | Should be "Skipped"
                    $result.time.ticks | should be 100
                }
    
                it "adds Pending test" {
                    $p.AddTestResult("result","Pending", 100)
                    $result = $p.TestResult[-1]
                    $result.Name | should be "result"
                    $result.passed | should be $true
                    $result.Result | Should be "Pending"
                    $result.time.ticks | should be 100
                }
    
                $p.LeaveTestGroup('Describe', 'Describe')
            }
    
            Context "Path and TestNameFilter parameter is set" {
                $strict = New-PesterState -Strict
    
                It "Keeps Passed state" {
                    $strict.AddTestResult("test","Passed")
                    $result = $strict.TestResult[-1]
    
                    $result.passed | should be $true
                    $result.Result | Should be "Passed"
                }
    
                It "Keeps Failed state" {
                    $strict.AddTestResult("test","Failed")
                    $result = $strict.TestResult[-1]
    
                    $result.passed | should be $false
                    $result.Result | Should be "Failed"
                }
    
                It "Changes Pending state to Failed" {
                    $strict.AddTestResult("test","Pending")
                    $result = $strict.TestResult[-1]
    
                    $result.passed | should be $false
                    $result.Result | Should be "Failed"
                }
    
                It "Changes Skipped state to Failed" {
                    $strict.AddTestResult("test","Skipped")
                    $result = $strict.TestResult[-1]
    
                    $result.passed | should be $false
                    $result.Result | Should be "Failed"
                }
            }
        }
    }
    
    Describe ConvertTo-FailureLines {
        # This technique is used, rather than InModuleScope, so we don't introduce a new $script: scope when invoking our
        # temporary file inside the Pester module (but we still need to resolve and invoke the internal ConvertTo-FailureLines
        # function for the unit tests.)
    
        $pesterModule = Get-Module Pester -ErrorAction Stop
        $convertToFailureLines = & $pesterModule { ${function:ConvertTo-FailureLines} }
    
        $testPath = Join-Path $TestDrive test.ps1
        $escapedTestPath = [regex]::Escape($testPath)
    
        It 'produces correct message lines.' {
            try { throw 'message' } catch { $e = $_ }
    
            $r = $e | & $convertToFailureLines
    
            $r.Message[0] | Should be 'RuntimeException: message'
            $r.Message.Count | Should be 1
        }
    
        It 'failed should produces correct message lines.' {
            try { 'One' | Should be 'Two' } catch { $e = $_ }
            $r = $e | & $convertToFailureLines
    
            $r.Message[0] | Should be 'String lengths are both 3. Strings differ at index 0.'
            $r.Message[1] | Should be 'Expected: {Two}'
            $r.Message[2] | Should be 'But was:  {One}'
            $r.Message[3] | Should be '-----------^'
            $r.Message[4] | Should match "'One' | Should be 'Two'"
            $r.Message.Count | Should be 5
        }
    
        Context 'should fails in file' {
            Set-Content -Path $testPath -Value @'
                $script:IgnoreErrorPreference = 'SilentlyContinue'
                'One' | Should be 'Two'
    '@
    
            try { & $testPath } catch { $e = $_ }
            $r = $e | & $convertToFailureLines
    
            It 'produces correct message lines.' {
                $r.Message[0] | Should be 'String lengths are both 3. Strings differ at index 0.'
                $r.Message[1] | Should be 'Expected: {Two}'
                $r.Message[2] | Should be 'But was:  {One}'
                $r.Message[3] | Should be '-----------^'
                $r.Message[4] | Should be "2:             'One' | Should be 'Two'"
                $r.Message.Count | Should be 5
            }
    
            if ( $e | Get-Member -Name ScriptStackTrace )
            {
                It 'produces correct trace lines.' {
                    $r.Trace[0] | Should be "at <ScriptBlock>, $testPath`: line 2"
                    $r.Trace[1] -match 'at <ScriptBlock>, .*\\Functions\\PesterState.Tests.ps1: line [0-9]*$' |
                        Should be $true
                    $r.Trace.Count | Should be 2
                }
            }
            else
            {
                It 'produces correct trace lines.' {
                    $r.Trace[0] | Should be "at line: 2 in $testPath"
                    $r.Trace.Count | Should be 1
                }
            }
        }
    
        Context 'exception thrown in nested functions in file' {
            Set-Content -Path $testPath -Value @'
                function f1 {
                    throw 'f1 message'
                }
                function f2 {
                    f1
                }
                f2
    '@
    
            try { & $testPath } catch { $e = $_ }
    
            $r = $e | & $convertToFailureLines
    
            It 'produces correct message lines.' {
                $r.Message[0] | Should be 'RuntimeException: f1 message'
            }
    
            if ( $e | Get-Member -Name ScriptStackTrace )
            {
                It 'produces correct trace lines.' {
                    $r.Trace[0] | Should be "at f1, $testPath`: line 2"
                    $r.Trace[1] | Should be "at f2, $testPath`: line 5"
                    $r.Trace[2] | Should be "at <ScriptBlock>, $testPath`: line 7"
                    $r.Trace[3] -match 'at <ScriptBlock>, .*\\Functions\\PesterState.Tests.ps1: line [0-9]*$' |
                        Should be $true
                    $r.Trace.Count | Should be 4
                }
            }
            else
            {
                It 'produces correct trace lines.' {
                    $r.Trace[0] | Should be "at line: 2 in $testPath"
                    $r.Trace.Count | Should be 1
                }
            }
        }
    
        Context 'nested exceptions thrown in file' {
            Set-Content -Path $testPath -Value @'
                try
                {
                    throw New-Object System.ArgumentException(
                        'inner message',
                        'param_name'
                    )
                }
                catch
                {
                    throw New-Object System.FormatException(
                        'outer message',
                        $_.Exception
                    )
                }
    '@
    
            try { & $testPath } catch { $e = $_ }
    
            $r = $e | & $convertToFailureLines
    
            It 'produces correct message lines.' {
                $r.Message[0] | Should be 'ArgumentException: inner message'
                $r.Message[1] | Should be 'Parameter name: param_name'
                $r.Message[2] | Should be 'FormatException: outer message'
            }
    
            if ( $e | Get-Member -Name ScriptStackTrace )
            {
                It 'produces correct trace line.' {
                    $r.Trace[0] | Should be "at <ScriptBlock>, $testPath`: line 10"
                    $r.Trace[1] -match 'at <ScriptBlock>, .*\\Functions\\PesterState.Tests.ps1: line [0-9]*$'
                    $r.Trace.Count | Should be 2
                }
            }
            else
            {
                It 'produces correct trace line.' {
                    $r.Trace[0] | Should be "at line: 10 in $testPath"
                    $r.Trace.Count | Should be 1
                }
            }
        }
    }
    
  • tools\Functions\SetupTeardown.ps1 Show
    function BeforeEach
    {
    <#
    .SYNOPSIS
        Defines a series of steps to perform at the beginning of every It block within
        the current Context or Describe block.
    
    .DESCRIPTION
        BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply
        to the entire Context or Describe block, regardless of the order of the
        statements in the Context or Describe.  For a full description of this
        behavior, as well as how multiple BeforeEach or AfterEach blocks interact
        with each other, please refer to the about_BeforeEach_AfterEach help file.
    
    .LINK
        about_BeforeEach_AfterEach
    #>
        Assert-DescribeInProgress -CommandName BeforeEach
    }
    
    function AfterEach
    {
    <#
    .SYNOPSIS
        Defines a series of steps to perform at the end of every It block within
        the current Context or Describe block.
    
    .DESCRIPTION
        BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply
        to the entire Context or Describe block, regardless of the order of the
        statements in the Context or Describe.  For a full description of this
        behavior, as well as how multiple BeforeEach or AfterEach blocks interact
        with each other, please refer to the about_BeforeEach_AfterEach help file.
    
    .LINK
        about_BeforeEach_AfterEach
    #>
        Assert-DescribeInProgress -CommandName AfterEach
    }
    
    function BeforeAll
    {
    <#
    .SYNOPSIS
        Defines a series of steps to perform at the beginning of the current Context
        or Describe block.
    
    .DESCRIPTION
        BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply
        to the entire Context or Describe block, regardless of the order of the
        statements in the Context or Describe.
    
    .LINK
        about_BeforeEach_AfterEach
    #>
        Assert-DescribeInProgress -CommandName BeforeAll
    }
    
    function AfterAll
    {
    <#
    .SYNOPSIS
        Defines a series of steps to perform at the end of every It block within
        the current Context or Describe block.
    
    .DESCRIPTION
        BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply
        to the entire Context or Describe block, regardless of the order of the
        statements in the Context or Describe.
    
    .LINK
        about_BeforeEach_AfterEach
    #>
        Assert-DescribeInProgress -CommandName AfterAll
    }
    
    function Invoke-TestCaseSetupBlocks
    {
        Invoke-Blocks -ScriptBlock $pester.GetTestCaseSetupBlocks()
    }
    
    function Invoke-TestCaseTeardownBlocks
    {
        Invoke-Blocks -ScriptBlock $pester.GetTestCaseTeardownBlocks()
    }
    
    function Invoke-TestGroupSetupBlocks
    {
        Invoke-Blocks -ScriptBlock $pester.GetCurrentTestGroupSetupBlocks()
    }
    
    function Invoke-TestGroupTeardownBlocks
    {
        Invoke-Blocks -ScriptBlock $pester.GetCurrentTestGroupTeardownBlocks()
    }
    
    function Invoke-Blocks
    {
        param ([scriptblock[]] $ScriptBlock)
    
        foreach ($block in $ScriptBlock)
        {
            if ($null -eq $block) { continue }
            $null = . $block
        }
    }
    
    function Add-SetupAndTeardown
    {
        param (
            [scriptblock] $ScriptBlock
        )
    
        if ($PSVersionTable.PSVersion.Major -le 2)
        {
            Add-SetupAndTeardownV2 -ScriptBlock $ScriptBlock
        }
        else
        {
            Add-SetupAndTeardownV3 -ScriptBlock $ScriptBlock
        }
    }
    
    function Add-SetupAndTeardownV3
    {
        param (
            [scriptblock] $ScriptBlock
        )
    
        $pattern = '^(?:Before|After)(?:Each|All)$'
        $predicate = {
            param ([System.Management.Automation.Language.Ast] $Ast)
    
            $Ast -is [System.Management.Automation.Language.CommandAst] -and
            $Ast.CommandElements.Count -eq 2 -and
            $Ast.CommandElements[0].ToString() -match $pattern -and
            $Ast.CommandElements[1] -is [System.Management.Automation.Language.ScriptBlockExpressionAst]
        }
    
        $searchNestedBlocks = $false
    
        $calls = $ScriptBlock.Ast.FindAll($predicate, $searchNestedBlocks)
    
        foreach ($call in $calls)
        {
            # For some reason, calling ScriptBlockAst.GetScriptBlock() sometimes blows up due to failing semantics
            # checks, even though the code is perfectly valid.  So we'll poke around with reflection again to skip
            # that part and just call the internal ScriptBlock constructor that we need
    
            $iPmdProviderType = [scriptblock].Assembly.GetType('System.Management.Automation.Language.IParameterMetadataProvider')
    
            $flags = [System.Reflection.BindingFlags]'Instance, NonPublic'
            $constructor = [scriptblock].GetConstructor($flags, $null, [Type[]]@($iPmdProviderType, [bool]), $null)
    
            $block = $constructor.Invoke(@($call.CommandElements[1].ScriptBlock, $false))
    
            Set-ScriptBlockScope -ScriptBlock $block -SessionState $pester.SessionState
            $commandName = $call.CommandElements[0].ToString()
            Add-SetupOrTeardownScriptBlock -CommandName $commandName -ScriptBlock $block
        }
    }
    
    function Add-SetupAndTeardownV2
    {
        param (
            [scriptblock] $ScriptBlock
        )
    
        $codeText = $ScriptBlock.ToString()
        $tokens = @(ParseCodeIntoTokens -CodeText $codeText)
    
        for ($i = 0; $i -lt $tokens.Count; $i++)
        {
            $token = $tokens[$i]
            $type = $token.Type
            if ($type -eq [System.Management.Automation.PSTokenType]::Command -and
                (IsSetupOrTeardownCommand -CommandName $token.Content))
            {
                $openBraceIndex, $closeBraceIndex = Get-BraceIndicesForCommand -Tokens $tokens -CommandIndex $i
    
                $block = Get-ScriptBlockFromTokens -Tokens $Tokens -OpenBraceIndex $openBraceIndex -CloseBraceIndex $closeBraceIndex -CodeText $codeText
                Add-SetupOrTeardownScriptBlock -CommandName $token.Content -ScriptBlock $block
    
                $i = $closeBraceIndex
            }
            elseif ($type -eq [System.Management.Automation.PSTokenType]::GroupStart)
            {
                # We don't want to parse Setup or Teardown commands in child scopes here, so anything
                # bounded by a GroupStart / GroupEnd token pair which is not immediately preceded by
                # a setup / teardown command name is ignored.
                $i = Get-GroupCloseTokenIndex -Tokens $tokens -GroupStartTokenIndex $i
            }
        }
    }
    
    function ParseCodeIntoTokens
    {
        param ([string] $CodeText)
    
        $parseErrors = $null
        $tokens = [System.Management.Automation.PSParser]::Tokenize($CodeText, [ref] $parseErrors)
    
        if ($parseErrors.Count -gt 0)
        {
            $currentScope = $pester.CurrentTestGroup.Hint
            if (-not $currentScope) { $currentScope = 'test group' }
            throw "The current $currentScope block contains syntax errors."
        }
    
        return $tokens
    }
    
    function IsSetupOrTeardownCommand
    {
        param ([string] $CommandName)
        return (IsSetupCommand -CommandName $CommandName) -or (IsTeardownCommand -CommandName $CommandName)
    }
    
    function IsSetupCommand
    {
        param ([string] $CommandName)
        return $CommandName -eq 'BeforeEach' -or $CommandName -eq 'BeforeAll'
    }
    
    function IsTeardownCommand
    {
        param ([string] $CommandName)
        return $CommandName -eq 'AfterEach' -or $CommandName -eq 'AfterAll'
    }
    
    function IsTestGroupCommand
    {
        param ([string] $CommandName)
        return $CommandName -eq 'BeforeAll' -or $CommandName -eq 'AfterAll'
    }
    
    function Get-BraceIndicesForCommand
    {
        param (
            [System.Management.Automation.PSToken[]] $Tokens,
            [int] $CommandIndex
        )
    
        $openingGroupTokenIndex = Get-GroupStartTokenForCommand -Tokens $Tokens -CommandIndex $CommandIndex
        $closingGroupTokenIndex = Get-GroupCloseTokenIndex -Tokens $Tokens -GroupStartTokenIndex $openingGroupTokenIndex
    
        return $openingGroupTokenIndex, $closingGroupTokenIndex
    }
    
    function Get-GroupStartTokenForCommand
    {
        param (
            [System.Management.Automation.PSToken[]] $Tokens,
            [int] $CommandIndex
        )
    
        # We may want to allow newlines, other parameters, etc at some point.  For now it's good enough to
        # just verify that the next token after our BeforeEach or AfterEach command is an opening curly brace.
    
        $commandName = $Tokens[$CommandIndex].Content
    
        if ($CommandIndex + 1 -ge $tokens.Count -or
            $tokens[$CommandIndex + 1].Type -ne [System.Management.Automation.PSTokenType]::GroupStart -or
            $tokens[$CommandIndex + 1].Content -ne '{')
        {
            throw "The $commandName command must be immediately followed by the opening brace of a script block."
        }
    
        return $CommandIndex + 1
    }
    
    & $SafeCommands['Add-Type'] -TypeDefinition @'
        namespace Pester
        {
            using System;
            using System.Management.Automation;
    
            public static class ClosingBraceFinder
            {
                public static int GetClosingBraceIndex(PSToken[] tokens, int startIndex)
                {
                    int groupLevel = 1;
                    int len = tokens.Length;
    
                    for (int i = startIndex + 1; i < len; i++)
                    {
                        PSTokenType type = tokens[i].Type;
                        if (type == PSTokenType.GroupStart)
                        {
                            groupLevel++;
                        }
                        else if (type == PSTokenType.GroupEnd)
                        {
                            groupLevel--;
    
                            if (groupLevel <= 0) { return i; }
                        }
                    }
    
                    return -1;
                }
            }
        }
    '@
    
    function Get-GroupCloseTokenIndex
    {
        param (
            [System.Management.Automation.PSToken[]] $Tokens,
            [int] $GroupStartTokenIndex
        )
    
        $closeIndex = [Pester.ClosingBraceFinder]::GetClosingBraceIndex($Tokens, $GroupStartTokenIndex)
    
        if ($closeIndex -lt 0)
        {
            throw 'No corresponding GroupEnd token was found.'
        }
    
        return $closeIndex
    }
    
    function Get-ScriptBlockFromTokens
    {
        param (
            [System.Management.Automation.PSToken[]] $Tokens,
            [int] $OpenBraceIndex,
            [int] $CloseBraceIndex,
            [string] $CodeText
        )
    
        $blockStart = $Tokens[$OpenBraceIndex + 1].Start
        $blockLength = $Tokens[$CloseBraceIndex].Start - $blockStart
        $setupOrTeardownCodeText = $codeText.Substring($blockStart, $blockLength)
    
        $scriptBlock = [scriptblock]::Create($setupOrTeardownCodeText)
        Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $pester.SessionState
    
        return $scriptBlock
    }
    
    function Add-SetupOrTeardownScriptBlock
    {
        param (
            [string] $CommandName,
            [scriptblock] $ScriptBlock
        )
    
        $Pester.AddSetupOrTeardownBlock($ScriptBlock, $CommandName)
    }
    
  • tools\Functions\SetupTeardown.Tests.ps1 Show
    Describe 'Describe-Scoped Test Case setup' {
        BeforeEach {
            $testVariable = 'From BeforeEach'
        }
    
        $testVariable = 'Set in Describe'
    
        It 'Assigns the correct value in first test' {
            $testVariable | Should Be 'From BeforeEach'
            $testVariable = 'Set in It'
        }
    
        It 'Assigns the correct value in subsequent tests' {
            $testVariable | Should Be 'From BeforeEach'
        }
    }
    
    Describe 'Context-scoped Test Case setup' {
        $testVariable = 'Set in Describe'
    
        Context 'The context' {
            BeforeEach {
                $testVariable = 'From BeforeEach'
            }
    
            It 'Assigns the correct value inside the context' {
                $testVariable | Should Be 'From BeforeEach'
            }
        }
    
        It 'Reports the original value after the Context' {
            $testVariable | Should Be 'Set in Describe'
        }
    }
    
    Describe 'Multiple Test Case setup blocks' {
        $testVariable = 'Set in Describe'
    
        BeforeEach {
            $testVariable = 'Set in Describe BeforeEach'
        }
    
        Context 'The context' {
            It 'Executes Describe setup blocks first, then Context blocks in the order they were defined (even if they are defined after the It block.)' {
                $testVariable | Should Be 'Set in the second Context BeforeEach'
            }
    
            BeforeEach {
                $testVariable = 'Set in the first Context BeforeEach'
            }
    
            BeforeEach {
                $testVariable = 'Set in the second Context BeforeEach'
            }
        }
    
        It 'Continues to execute Describe setup blocks after the Context' {
            $testVariable | Should Be 'Set in Describe BeforeEach'
        }
    }
    
    Describe 'Describe-scoped Test Case teardown' {
        $testVariable = 'Set in Describe'
    
        AfterEach {
            $testVariable = 'Set in AfterEach'
        }
    
        It 'Does not modify the variable before the first test' {
            $testVariable | Should Be 'Set in Describe'
        }
    
        It 'Modifies the variable after the first test' {
            $testVariable | Should Be 'Set in AfterEach'
        }
    }
    
    Describe 'Multiple Test Case teardown blocks' {
        $testVariable = 'Set in Describe'
    
        AfterEach {
            $testVariable = 'Set in Describe AfterEach'
        }
    
        Context 'The context' {
            AfterEach {
                $testVariable = 'Set in the first Context AfterEach'
            }
    
            It 'Performs a test in Context' { "some output to prevent the It being marked as Pending and failing because of Strict mode"}
    
            It 'Executes Describe teardown blocks after Context teardown blocks' {
                $testVariable | Should Be 'Set in the second Describe AfterEach'
            }
        }
    
        AfterEach {
            $testVariable = 'Set in the second Describe AfterEach'
        }
    }
    
    $script:DescribeBeforeAllCounter = 0
    $script:DescribeAfterAllCounter  = 0
    $script:ContextBeforeAllCounter  = 0
    $script:ContextAfterAllCounter   = 0
    
    Describe 'Test Group Setup and Teardown' {
        It 'Executed the Describe BeforeAll regardless of definition order' {
            $script:DescribeBeforeAllCounter | Should Be 1
        }
    
        It 'Did not execute any other block yet' {
            $script:DescribeAfterAllCounter | Should Be 0
            $script:ContextBeforeAllCounter | Should Be 0
            $script:ContextAfterAllCounter  | Should Be 0
        }
    
        BeforeAll {
            $script:DescribeBeforeAllCounter++
        }
    
        AfterAll {
            $script:DescribeAfterAllCounter++
        }
    
        Context 'Context scoped setup and teardown' {
            BeforeAll {
                $script:ContextBeforeAllCounter++
            }
    
            AfterAll {
                $script:ContextAfterAllCounter++
            }
    
            It 'Executed the Context BeforeAll block' {
                $script:ContextBeforeAllCounter | Should Be 1
            }
    
            It 'Has not executed any other blocks yet' {
                $script:DescribeBeforeAllCounter | Should Be 1
                $script:DescribeAfterAllCounter  | Should Be 0
                $script:ContextAfterAllCounter   | Should Be 0
            }
        }
    
        It 'Executed the Context AfterAll block' {
            $script:ContextAfterAllCounter | Should Be 1
        }
    }
    
    Describe 'Finishing TestGroup Setup and Teardown tests' {
        It 'Executed each Describe and Context group block once' {
            $script:DescribeBeforeAllCounter | Should Be 1
            $script:DescribeAfterAllCounter  | Should Be 1
            $script:ContextBeforeAllCounter  | Should Be 1
            $script:ContextAfterAllCounter   | Should Be 1
        }
    }
    
    
    if ($PSVersionTable.PSVersion.Major -ge 3)
    {
        $thisTestScriptFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($PSCommandPath)
    
        Describe 'Script Blocks and file association (testing automatic variables)' {
            BeforeEach {
                $commandPath = $PSCommandPath
            }
    
            $beforeEachBlock = InModuleScope Pester {
                $pester.CurrentTestGroup.BeforeEach[0]
            }
    
            It 'Creates script block objects associated with the proper file' {
                $scriptBlockFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($beforeEachBlock.File)
    
                $scriptBlockFilePath | Should Be $thisTestScriptFilePath
            }
    
            It 'Has the correct automatic variable values inside the BeforeEach block' {
                $commandPath | Should Be $PSCommandPath
            }
        }
    }
    
    #Testing if failing setup or teardown will fail 'It' is done in the TestsRunningInCleanRunspace.Tests.ps1 file
    
  • tools\Functions\TestDrive.ps1 Show
    #
    function New-TestDrive ([Switch]$PassThru, [string] $Path) {
        if ($Path -notmatch '\S')
        {
            $directory = New-RandomTempDirectory
        }
        else
        {
            if (-not (& $SafeCommands['Test-Path'] -Path $Path))
            {
                & $SafeCommands['New-Item'] -ItemType Container -Path $Path | & $SafeCommands['Out-Null']
            }
    
            $directory = & $SafeCommands['Get-Item'] $Path
        }
    
        $DriveName = "TestDrive"
    
        #setup the test drive
        if ( -not (& $SafeCommands['Test-Path'] "${DriveName}:\") )
        {
            & $SafeCommands['New-PSDrive'] -Name $DriveName -PSProvider FileSystem -Root $directory -Scope Global -Description "Pester test drive" | & $SafeCommands['Out-Null']
        }
    
        #publish the global TestDrive variable used in few places within the module
        if (-not (& $SafeCommands['Test-Path'] "Variable:Global:$DriveName"))
        {
            & $SafeCommands['New-Variable'] -Name $DriveName -Scope Global -Value $directory
        }
    
        if ( $PassThru ) { & $SafeCommands['Get-PSDrive'] -Name $DriveName }
    }
    
    
    function Clear-TestDrive ([String[]]$Exclude) {
        $Path = (& $SafeCommands['Get-PSDrive'] -Name TestDrive).Root
        if (& $SafeCommands['Test-Path'] -Path $Path )
        {
            #Get-ChildItem -Exclude did not seem to work with full paths
            & $SafeCommands['Get-ChildItem'] -Recurse -Path $Path |
            & $SafeCommands['Sort-Object'] -Descending  -Property "FullName" |
            & $SafeCommands['Where-Object'] { $Exclude -NotContains $_.FullName } |
            & $SafeCommands['Remove-Item'] -Force -Recurse
        }
    }
    
    function New-RandomTempDirectory {
        do
        {
            $tempPath = Get-TempDirectory
            $Path = & $SafeCommands['Join-Path'] -Path $tempPath -ChildPath ([Guid]::NewGuid())
        } until (-not (& $SafeCommands['Test-Path'] -Path $Path ))
    
        & $SafeCommands['New-Item'] -ItemType Container -Path $Path
    }
    
    function Get-TestDriveItem {
    <#
        .SYNOPSIS
        The Get-TestDriveItem cmdlet gets the item in Pester test drive.
    
        .DESCRIPTION
        The Get-TestDriveItem cmdlet gets the item in Pester test drive. It does not
        get the contents of the item at the location unless you use a wildcard
        character (*) to request all the contents of the item.
    
        The function Get-TestDriveItem is deprecated since Pester v. 4.0
        and will be deleted in the next major version of Pester.
    
        .PARAMETER Path
        Specifies the path to an item. The path need to be relative to TestDrive:.
        This cmdlet gets the item at the specified location. Wildcards are permitted.
        This parameter is required, but the parameter name ("Path") is optional.
    
        .LINK
        https://github.com/pester/Pester/wiki/TestDrive
        about_TestDrive
    #>
    
        #moved here from Pester.psm1
        param ([string]$Path)
    
        & $SafeCommands['Write-Warning'] -Message "The function Get-TestDriveItem is deprecated since Pester 4.0.0 and will be removed from Pester 5.0.0."
    
        Assert-DescribeInProgress -CommandName Get-TestDriveItem
        & $SafeCommands['Get-Item'] $(& $SafeCommands['Join-Path'] $TestDrive $Path )
    }
    
    function Get-TestDriveChildItem {
        $Path = (& $SafeCommands['Get-PSDrive'] -Name TestDrive).Root
        if (& $SafeCommands['Test-Path'] -Path $Path )
        {
            & $SafeCommands['Get-ChildItem'] -Recurse -Path $Path
        }
    }
    
    function Remove-TestDrive {
    
        $DriveName = "TestDrive"
        $Drive = & $SafeCommands['Get-PSDrive'] -Name $DriveName -ErrorAction $script:IgnoreErrorPreference
        $Path = ($Drive).Root
    
    
        if ($pwd -like "$DriveName*" ) {
            #will staying in the test drive cause issues?
            #TODO review this
            & $SafeCommands['Write-Warning'] -Message "Your current path is set to ${pwd}:. You should leave ${DriveName}:\ before leaving Describe."
        }
    
        if ( $Drive )
        {
            $Drive | & $SafeCommands['Remove-PSDrive'] -Force -ErrorAction $script:IgnoreErrorPreference
        }
    
        if (& $SafeCommands['Test-Path'] -Path $Path)
        {
            & $SafeCommands['Remove-Item'] -Path $Path -Force -Recurse
        }
    
        if (& $SafeCommands['Get-Variable'] -Name $DriveName -Scope Global -ErrorAction $script:IgnoreErrorPreference) {
            & $SafeCommands['Remove-Variable'] -Scope Global -Name $DriveName -Force
        }
    }
    
    function Setup {
        #included for backwards compatibility
        param(
        [switch]$Dir,
        [switch]$File,
        $Path,
        $Content = "",
        [switch]$PassThru
        )
    
        Assert-DescribeInProgress -CommandName Setup
    
        $TestDriveName = & $SafeCommands['Get-PSDrive'] TestDrive |
                         & $SafeCommands['Select-Object'] -ExpandProperty Root
    
        if ($Dir) {
            $item = & $SafeCommands['New-Item'] -Name $Path -Path "${TestDriveName}\" -Type Container -Force
        }
        if ($File) {
            $item = $Content | & $SafeCommands['New-Item'] -Name $Path -Path "${TestDriveName}\" -Type File -Force
        }
    
        if($PassThru) {
            return $item
        }
    }
    
  • tools\Functions\TestDrive.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    if ($PSVersionTable.PSVersion.Major -lt 6 -or $IsWindows)
    {
        $tempPath = $env:TEMP
    }
    else
    {
        $tempPath = '/tmp'
    }
    
    Describe "Setup" {
        It "returns a location that is in a temp area" {
            $testDrivePath = (Get-Item $TestDrive).FullName
            $testDrivePath -like "$tempPath*" | Should Be $true
        }
    
        It "creates a drive location called TestDrive:" {
            "TestDrive:\" | Should Exist
        }
    }
    
    Describe "TestDrive" {
        It "handles creation of a drive with . characters in the path" {
            #TODO: currently untested but requirement needs to be here
            "preventing this from failing"
        }
    }
    
    Describe "Create filesystem with directories" {
        Setup -Dir "dir1"
        Setup -Dir "dir2"
    
        It "creates directory when called with no file content" {
            "TestDrive:\dir1" | Should Exist
        }
    
        It "creates another directory when called with no file content and doesnt remove first directory" {
            $result = Test-Path "TestDrive:\dir2"
            $result = $result -and (Test-Path "TestDrive:\dir1")
            $result | Should Be $true
        }
    }
    
    Describe "Create nested directory structure" {
        Setup -Dir "parent/child"
    
        It "creates parent directory" {
            "TestDrive:\parent" | Should Exist
        }
    
        It "creates child directory underneath parent" {
            "TestDrive:\parent\child" | Should Exist
        }
    }
    
    Describe "Create a file with no content" {
        Setup -File "file"
    
        It "creates file" {
            "TestDrive:\file" | Should Exist
        }
    
        It "also has no content" {
            Get-Content "TestDrive:\file" | Should BeNullOrEmpty
        }
    }
    
    Describe "Create a file with content" {
        Setup -File "file" "file contents"
    
        It "creates file" {
            "TestDrive:\file" | Should Exist
        }
    
        It "adds content to the file" {
            Get-Content "TestDrive:\file" | Should Be "file contents"
        }
    }
    
    Describe "Create file with passthru" {
        $thefile = Setup -File "thefile" -PassThru
    
        It "returns the file from the temp location" {
            $thefile.FullName -like "$tempPath*" | Should Be $true
            $thefile.Exists | Should Be $true
        }
    }
    
    Describe "Create directory with passthru" {
        $thedir = Setup -Dir "thedir" -PassThru
    
        It "returns the directory from the temp location" {
            $thedir.FullName -like "$tempPath*" | Should Be $true
            $thedir.Exists | Should Be $true
        }
    }
    
    Describe "TestDrive scoping" {
        $describe = Setup -File 'Describe' -PassThru
        Context "Describe file is available in context" {
            It "Finds the file" {
                $describe | Should Exist
            }
            #create file for the next test
            Setup -File 'Context'
    
            It "Creates It-scoped contents" {
                Setup -File 'It'
                'TestDrive:\It' | Should Exist
            }
    
            It "Does not clear It-scoped contents on exit" {
                'TestDrive:\It' | Should Exist
            }
        }
    
        It "Context file are removed when returning to Describe" {
            "TestDrive:\Context" | Should Not Exist
        }
    
        It "Describe file is still available in Describe" {
            $describe | Should Exist
        }
    }
    
    Describe "Cleanup" {
        Setup -Dir "foo"
    }
    
    Describe "Cleanup" {
        It "should have removed the temp folder from the previous fixture" {
            Test-Path "$TestDrive\foo" | Should Not Exist
        }
    
        It "should also remove the TestDrive:" {
            Test-Path "TestDrive:\foo" | Should Not Exist
        }
    }
    
    Describe "Cleanup when Remove-Item is mocked" {
        Mock Remove-Item {}
    
        Context "add a temp directory" {
            Setup -Dir "foo"
        }
    
        Context "next context" {
    
            It "should have removed the temp folder" {
                "$TestDrive\foo" | Should Not Exist
            }
    
        }
    }
    
    InModuleScope Pester {
        Describe "New-RandomTempDirectory" {
            It "creates randomly named directory" {
                $first = New-RandomTempDirectory
                $second = New-RandomTempDirectory
    
                $first | Remove-Item -Force
                $second | Remove-Item -Force
    
                $first.name | Should Not Be $second.name
    
            }
        }
    }
    
  • tools\Functions\TestResults.ps1 Show
    function Get-HumanTime($Seconds) {
        if($Seconds -gt 0.99) {
            $time = [math]::Round($Seconds, 2)
            $unit = 's'
        }
        else {
            $time = [math]::Floor($Seconds * 1000)
            $unit = 'ms'
        }
        return "$time$unit"
    }
    
    function GetFullPath ([string]$Path) {
        if (-not [System.IO.Path]::IsPathRooted($Path))
        {
            $Path = & $SafeCommands['Join-Path'] $ExecutionContext.SessionState.Path.CurrentFileSystemLocation $Path
        }
    
        return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
    }
    
    function Export-PesterResults
    {
        param (
            $PesterState,
            [string] $Path,
            [string] $Format
        )
    
        switch ($Format)
        {
            'NUnitXml'       { Export-NUnitReport -PesterState $PesterState -Path $Path }
    
            default
            {
                throw "'$Format' is not a valid Pester export format."
            }
        }
    }
    function Export-NUnitReport {
        param (
            [parameter(Mandatory=$true,ValueFromPipeline=$true)]
            $PesterState,
    
            [parameter(Mandatory=$true)]
            [String]$Path
        )
    
        #the xmlwriter create method can resolve relatives paths by itself. but its current directory might
        #be different from what PowerShell sees as the current directory so I have to resolve the path beforehand
        #working around the limitations of Resolve-Path
    
        $Path = GetFullPath -Path $Path
    
        $settings = & $SafeCommands['New-Object'] -TypeName Xml.XmlWriterSettings -Property @{
            Indent = $true
            NewLineOnAttributes = $false
        }
    
        $xmlFile = $null
        $xmlWriter = $null
        try {
            $xmlFile = [IO.File]::Create($Path)
            $xmlWriter = [Xml.XmlWriter]::Create($xmlFile, $settings)
    
            Write-NUnitReport -XmlWriter $xmlWriter -PesterState $PesterState
    
            $xmlWriter.Flush()
            $xmlFile.Flush()
        }
        finally
        {
            if ($null -ne $xmlWriter) {
                try { $xmlWriter.Close() } catch {}
            }
            if ($null -ne $xmlFile) {
                try { $xmlFile.Close() } catch {}
            }
        }
    }
    
    function Write-NUnitReport($PesterState, [System.Xml.XmlWriter] $XmlWriter)
    {
        # Write the XML Declaration
        $XmlWriter.WriteStartDocument($false)
    
        # Write Root Element
        $xmlWriter.WriteStartElement('test-results')
    
        Write-NUnitTestResultAttributes @PSBoundParameters
        Write-NUnitTestResultChildNodes @PSBoundParameters
    
        $XmlWriter.WriteEndElement()
    }
    
    function Write-NUnitTestResultAttributes($PesterState, [System.Xml.XmlWriter] $XmlWriter)
    {
        $XmlWriter.WriteAttributeString('xmlns','xsi', $null, 'http://www.w3.org/2001/XMLSchema-instance')
        $XmlWriter.WriteAttributeString('xsi','noNamespaceSchemaLocation', [Xml.Schema.XmlSchema]::InstanceNamespace , 'nunit_schema_2.5.xsd')
        $XmlWriter.WriteAttributeString('name','Pester')
        $XmlWriter.WriteAttributeString('total', ($PesterState.TotalCount - $PesterState.SkippedCount))
        $XmlWriter.WriteAttributeString('errors', '0')
        $XmlWriter.WriteAttributeString('failures', $PesterState.FailedCount)
        $XmlWriter.WriteAttributeString('not-run', '0')
        $XmlWriter.WriteAttributeString('inconclusive', $PesterState.PendingCount + $PesterState.InconclusiveCount)
        $XmlWriter.WriteAttributeString('ignored', $PesterState.SkippedCount)
        $XmlWriter.WriteAttributeString('skipped', '0')
        $XmlWriter.WriteAttributeString('invalid', '0')
        $date = & $SafeCommands['Get-Date']
        $XmlWriter.WriteAttributeString('date', (& $SafeCommands['Get-Date'] -Date $date -Format 'yyyy-MM-dd'))
        $XmlWriter.WriteAttributeString('time', (& $SafeCommands['Get-Date'] -Date $date -Format 'HH:mm:ss'))
    }
    
    function Write-NUnitTestResultChildNodes($PesterState, [System.Xml.XmlWriter] $XmlWriter)
    {
        Write-NUnitEnvironmentInformation @PSBoundParameters
        Write-NUnitCultureInformation @PSBoundParameters
    
        $suiteInfo = Get-TestSuiteInfo -TestSuite $PesterState -TestSuiteName $PesterState.TestSuiteName
    
        $XmlWriter.WriteStartElement('test-suite')
    
        Write-NUnitTestSuiteAttributes -TestSuiteInfo $suiteInfo -XmlWriter $XmlWriter
    
        $XmlWriter.WriteStartElement('results')
    
        foreach ($action in $PesterState.TestActions.Actions)
        {
            Write-NUnitTestSuiteElements -XmlWriter $XmlWriter -Node $action
        }
    
        $XmlWriter.WriteEndElement()
        $XmlWriter.WriteEndElement()
    }
    
    function Write-NUnitEnvironmentInformation($PesterState, [System.Xml.XmlWriter] $XmlWriter)
    {
        $XmlWriter.WriteStartElement('environment')
    
        $environment = Get-RunTimeEnvironment
        foreach ($keyValuePair in $environment.GetEnumerator()) {
            $XmlWriter.WriteAttributeString($keyValuePair.Name, $keyValuePair.Value)
        }
    
        $XmlWriter.WriteEndElement()
    }
    
    function Write-NUnitCultureInformation($PesterState, [System.Xml.XmlWriter] $XmlWriter)
    {
        $XmlWriter.WriteStartElement('culture-info')
    
        $XmlWriter.WriteAttributeString('current-culture', ([System.Threading.Thread]::CurrentThread.CurrentCulture).Name)
        $XmlWriter.WriteAttributeString('current-uiculture', ([System.Threading.Thread]::CurrentThread.CurrentUiCulture).Name)
    
        $XmlWriter.WriteEndElement()
    }
    
    function Write-NUnitTestSuiteElements($Node, [System.Xml.XmlWriter] $XmlWriter, [string] $Path)
    {
        $suiteInfo = Get-TestSuiteInfo $Node
    
        $XmlWriter.WriteStartElement('test-suite')
    
        Write-NUnitTestSuiteAttributes -TestSuiteInfo $suiteInfo -XmlWriter $XmlWriter
    
        $XmlWriter.WriteStartElement('results')
    
        $separator = if ($Path) { '.' } else { '' }
        $newName = if ($Node.Hint -ne 'Script') { $suiteInfo.Name } else { '' }
        $newPath = "${Path}${separator}${newName}"
    
        foreach ($action in $Node.Actions)
        {
            if ($action.Type -eq 'TestGroup')
            {
                Write-NUnitTestSuiteElements -Node $action -XmlWriter $XmlWriter -Path $newPath
            }
        }
    
        $suites = @(
            $Node.Actions |
            & $SafeCommands['Where-Object'] { $_.Type -eq 'TestCase' } |
            & $SafeCommands['Group-Object'] -Property ParameterizedSuiteName
        )
    
        foreach ($suite in $suites)
        {
            if ($suite.Name)
            {
                $parameterizedSuiteInfo = Get-ParameterizedTestSuiteInfo -TestSuiteGroup $suite
    
                $XmlWriter.WriteStartElement('test-suite')
    
                Write-NUnitTestSuiteAttributes -TestSuiteInfo $parameterizedSuiteInfo -TestSuiteType 'ParameterizedTest' -XmlWriter $XmlWriter -Path $newPath
    
                $XmlWriter.WriteStartElement('results')
            }
    
            foreach ($testCase in $suite.Group)
            {
                Write-NUnitTestCaseElement -TestResult $testCase -XmlWriter $XmlWriter -Path $newPath -ParameterizedSuiteName $suite.Name
            }
    
            if ($suite.Name)
            {
                $XmlWriter.WriteEndElement()
                $XmlWriter.WriteEndElement()
            }
        }
    
        $XmlWriter.WriteEndElement()
        $XmlWriter.WriteEndElement()
    }
    
    function Get-ParameterizedTestSuiteInfo ([Microsoft.PowerShell.Commands.GroupInfo] $TestSuiteGroup)
    {
        $node = & $SafeCommands['New-Object'] psobject -Property @{
            Name              = $TestSuiteGroup.Name
            TotalCount        = 0
            Time              = [timespan]0
            PassedCount       = 0
            FailedCount       = 0
            SkippedCount      = 0
            PendingCount      = 0
            InconclusiveCount = 0
        }
    
        foreach ($testCase in $TestSuiteGroup.Group)
        {
            $node.TotalCount++
    
            switch ($testCase.Result)
            {
                Passed       { $Node.PassedCount++;       break; }
                Failed       { $Node.FailedCount++;       break; }
                Skipped      { $Node.SkippedCount++;      break; }
                Pending      { $Node.PendingCount++;      break; }
                Inconclusive { $Node.InconclusiveCount++; break; }
            }
    
            $Node.Time += $testCase.Time
        }
    
        return Get-TestSuiteInfo -TestSuite $node
    }
    
    function Get-TestSuiteInfo ($TestSuite, $TestSuiteName)
    {
        if (-not $PSBoundParameters.ContainsKey('TestSuiteName')) { $TestSuiteName = $TestSuite.Name }
    
        $suite = @{
            resultMessage = 'Failure'
            success       = if ($TestSuite.FailedCount -eq 0) { 'True' } else { 'False' }
            totalTime     = Convert-TimeSpan $TestSuite.Time
            name          = $TestSuiteName
            description   = $TestSuiteName
        }
    
        $suite.resultMessage = Get-GroupResult $TestSuite
        $suite
    }
    
    function Get-TestTime($tests) {
        [TimeSpan]$totalTime = 0;
        if ($tests)
        {
            foreach ($test in $tests)
            {
                $totalTime += $test.time
            }
        }
    
        Convert-TimeSpan -TimeSpan $totalTime
    }
    function Convert-TimeSpan {
        param (
            [Parameter(ValueFromPipeline=$true)]
            $TimeSpan
        )
        process {
            if ($TimeSpan) {
                [string][math]::round(([TimeSpan]$TimeSpan).totalseconds,4)
            }
            else
            {
                '0'
            }
        }
    }
    function Get-TestSuccess($tests) {
        $result = $true
        if ($tests)
        {
            foreach ($test in $tests) {
                if (-not $test.Passed) {
                    $result = $false
                    break
                }
            }
        }
        [String]$result
    }
    function Write-NUnitTestSuiteAttributes($TestSuiteInfo, [string] $TestSuiteType = 'TestFixture', [System.Xml.XmlWriter] $XmlWriter, [string] $Path)
    {
        $name = $TestSuiteInfo.Name
    
        if ($TestSuiteType -eq 'ParameterizedTest' -and $Path)
        {
            $name = "$Path.$name"
        }
    
        $XmlWriter.WriteAttributeString('type', $TestSuiteType)
        $XmlWriter.WriteAttributeString('name', $name)
        $XmlWriter.WriteAttributeString('executed', 'True')
        $XmlWriter.WriteAttributeString('result', $TestSuiteInfo.resultMessage)
        $XmlWriter.WriteAttributeString('success', $TestSuiteInfo.success)
        $XmlWriter.WriteAttributeString('time',$TestSuiteInfo.totalTime)
        $XmlWriter.WriteAttributeString('asserts','0')
        $XmlWriter.WriteAttributeString('description', $TestSuiteInfo.Description)
    }
    
    function Write-NUnitTestCaseElement($TestResult, [System.Xml.XmlWriter] $XmlWriter, [string] $ParameterizedSuiteName, [string] $Path)
    {
        $XmlWriter.WriteStartElement('test-case')
    
        Write-NUnitTestCaseAttributes -TestResult $TestResult -XmlWriter $XmlWriter -ParameterizedSuiteName $ParameterizedSuiteName -Path $Path
    
        $XmlWriter.WriteEndElement()
    }
    
    function Write-NUnitTestCaseAttributes($TestResult, [System.Xml.XmlWriter] $XmlWriter, [string] $ParameterizedSuiteName, [string] $Path)
    {
        $testName = $TestResult.Name
    
        if ($testName -eq $ParameterizedSuiteName)
        {
            $paramString = ''
            if ($null -ne $TestResult.Parameters)
            {
                $params = @(
                    foreach ($value in $TestResult.Parameters.Values)
                    {
                        if ($null -eq $value)
                        {
                            'null'
                        }
                        elseif ($value -is [string])
                        {
                            '"{0}"' -f $value
                        }
                        else
                        {
                            #do not use .ToString() it uses the current culture settings
                            #and we need to use en-US culture, which [string] or .ToString([Globalization.CultureInfo]'en-us') uses
                            [string]$value
                        }
                    }
                )
    
                $paramString = $params -join ','
            }
    
            $testName = "$testName($paramString)"
        }
    
        $separator = if ($Path) { '.' } else { '' }
        $testName = "${Path}${separator}${testName}"
    
        $XmlWriter.WriteAttributeString('description', $TestResult.Name)
    
        $XmlWriter.WriteAttributeString('name', $testName)
        $XmlWriter.WriteAttributeString('time', (Convert-TimeSpan $TestResult.Time))
        $XmlWriter.WriteAttributeString('asserts', '0')
        $XmlWriter.WriteAttributeString('success', $TestResult.Passed)
    
        switch ($TestResult.Result)
        {
            Passed
            {
                $XmlWriter.WriteAttributeString('result', 'Success')
                $XmlWriter.WriteAttributeString('executed', 'True')
                break
            }
            Skipped
            {
                $XmlWriter.WriteAttributeString('result', 'Ignored')
                $XmlWriter.WriteAttributeString('executed', 'False')
                break
            }
    
            Pending
            {
                $XmlWriter.WriteAttributeString('result', 'Inconclusive')
                $XmlWriter.WriteAttributeString('executed', 'True')
                break
            }
            Inconclusive
            {
                $XmlWriter.WriteAttributeString('result', 'Inconclusive')
                $XmlWriter.WriteAttributeString('executed', 'True')
    
                if ($TestResult.FailureMessage)
                {
                    $XmlWriter.WriteStartElement('reason')
                    $xmlWriter.WriteElementString('message', $TestResult.FailureMessage)
                    $XmlWriter.WriteEndElement() # Close reason tag
                }
    
                break
            }
            Failed
            {
                $XmlWriter.WriteAttributeString('result', 'Failure')
                $XmlWriter.WriteAttributeString('executed', 'True')
                $XmlWriter.WriteStartElement('failure')
                $xmlWriter.WriteElementString('message', $TestResult.FailureMessage)
                $XmlWriter.WriteElementString('stack-trace', $TestResult.StackTrace)
                $XmlWriter.WriteEndElement() # Close failure tag
                break
            }
        }
    }
    function Get-RunTimeEnvironment() {
        # based on what we found during startup, use the appropriate cmdlet
        if ( $SafeCommands['Get-CimInstance'] -ne $null )
        {
            $osSystemInformation = (& $SafeCommands['Get-CimInstance'] Win32_OperatingSystem)
        }
        elseif ( $SafeCommands['Get-WmiObject'] -ne $null )
        {
            $osSystemInformation = (& $SafeCommands['Get-WmiObject'] Win32_OperatingSystem)
        }
        else
        {
            $osSystemInformation = @{
                Name = "Unknown"
                Version = "0.0.0.0"
                }
        }
        @{
            'nunit-version' = '2.5.8.0'
            'os-version' = $osSystemInformation.Version
            platform = $osSystemInformation.Name
            cwd = (& $SafeCommands['Get-Location']).Path #run path
            'machine-name' = $env:ComputerName
            user = $env:Username
            'user-domain' = $env:userDomain
            'clr-version' = [string]$PSVersionTable.ClrVersion
        }
    }
    
    function Exit-WithCode ($FailedCount) {
        $host.SetShouldExit($FailedCount)
    }
    
    function Get-GroupResult ($InputObject)
    {
        #I am not sure about the result precedence, and can't find any good source
        #TODO: Confirm this is the correct order of precedence
        if ($inputObject.FailedCount  -gt 0) { return 'Failure' }
        if ($InputObject.SkippedCount -gt 0) { return 'Ignored' }
        if ($InputObject.PendingCount -gt 0) { return 'Inconclusive' }
        return 'Success'
    }
    
  • tools\Functions\TestResults.Tests.ps1 Show
    Set-StrictMode -Version Latest
    
    InModuleScope Pester {
        Describe "Write nunit test results" {
            Setup -Dir "Results"
    
            It "should write a successful test result" {
                #create state
                $TestResults = New-PesterState -Path TestDrive:\
                $testResults.EnterTestGroup('Mocked Describe', 'Describe')
                $TestResults.AddTestResult("Successful testcase",'Passed',(New-TimeSpan -Seconds 1))
    
                #export and validate the file
                $testFile = "$TestDrive\Results\Tests.xml"
                Export-NunitReport $testResults $testFile
                $xmlResult = [xml] (Get-Content $testFile)
                $xmlTestCase = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-case'
                $xmlTestCase.name     | Should Be "Mocked Describe.Successful testcase"
                $xmlTestCase.result   | Should Be "Success"
                $xmlTestCase.time     | Should Be "1"
            }
    
            It "should write a failed test result" {
                #create state
                $TestResults = New-PesterState -Path TestDrive:\
                $testResults.EnterTestGroup('Mocked Describe', 'Describe')
                $time = [TimeSpan]25000000 #2.5 seconds
                $TestResults.AddTestResult("Failed testcase",'Failed',$time,'Assert failed: "Expected: Test. But was: Testing"','at line: 28 in  C:\Pester\Result.Tests.ps1')
    
                #export and validate the file
                $testFile = "$TestDrive\Results\Tests.xml"
                Export-NunitReport $testResults $testFile
                $xmlResult = [xml] (Get-Content $testFile)
                $xmlTestCase = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-case'
                $xmlTestCase.name                   | Should Be "Mocked Describe.Failed testcase"
                $xmlTestCase.result                 | Should Be "Failure"
                $xmlTestCase.time                   | Should Be "2.5"
                $xmlTestCase.failure.message        | Should Be 'Assert failed: "Expected: Test. But was: Testing"'
                $xmlTestCase.failure.'stack-trace'  | Should Be 'at line: 28 in  C:\Pester\Result.Tests.ps1'
            }
    
             It "should write the test summary" {
                #create state
                $TestResults = New-PesterState -Path TestDrive:\
                $testResults.EnterTestGroup('Mocked Describe', 'Describe')
                $TestResults.AddTestResult("Testcase",'Passed',(New-TimeSpan -Seconds 1))
    
                #export and validate the file
                $testFile = "$TestDrive\Results\Tests.xml"
                Export-NunitReport $testResults $testFile
                $xmlResult = [xml] (Get-Content $testFile)
                $xmlTestResult = $xmlResult.'test-results'
                $xmlTestResult.total    | Should Be 1
                $xmlTestResult.failures | Should Be 0
                $xmlTestResult.date     | Should Not BeNullOrEmpty
                $xmlTestResult.time     | Should Not BeNullOrEmpty
            }
    
            it "should write the test-suite information" {
                #create state
                $TestResults = New-PesterState -Path TestDrive:\
                $testResults.EnterTestGroup('Mocked Describe', 'Describe')
                $TestResults.AddTestResult("Successful testcase",'Passed',[timespan]10000000) #1.0 seconds
                $TestResults.AddTestResult("Successful testcase",'Passed',[timespan]11000000) #1.1 seconds
                $testResults.LeaveTestGroup('Mocked Describe', 'Describe')
    
                Set-PesterStatistics -Node $TestResults.TestActions
    
                #export and validate the file
                $testFile = "$TestDrive\Results\Tests.xml"
                Export-NunitReport $testResults $testFile
                $xmlResult = [xml] (Get-Content $testFile)
    
                $xmlTestResult = $xmlResult.'test-results'.'test-suite'.results.'test-suite'
                $xmlTestResult.type            | Should Be "TestFixture"
                $xmlTestResult.name            | Should Be "Mocked Describe"
                $xmlTestResult.description     | Should Be "Mocked Describe"
                $xmlTestResult.result          | Should Be "Success"
                $xmlTestResult.success         | Should Be "True"
                $xmlTestResult.time            | Should Be 2.1
            }
    
            it "should write two test-suite elements for two describes" {
                #create state
                $TestResults = New-PesterState -Path TestDrive:\
                $testResults.EnterTestGroup('Describe #1', 'Describe')
                $TestResults.AddTestResult("Successful testcase",'Passed',(New-TimeSpan -Seconds 1))
                $TestResults.LeaveTestGroup('Describe #1', 'Describe')
                $testResults.EnterTestGroup('Describe #2', 'Describe')
                $TestResults.AddTestResult("Failed testcase",'Failed',(New-TimeSpan -Seconds 2))
                $TestResults.LeaveTestGroup('Describe #2', 'Describe')
    
                Set-PesterStatistics -Node $TestResults.TestActions
    
                #export and validate the file
                $testFile = "$TestDrive\Results\Tests.xml"
                Export-NunitReport $testResults $testFile
                $xmlResult = [xml] (Get-Content $testFile)
    
                $xmlTestSuite1 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[0]
                $xmlTestSuite1.name        | Should Be "Describe #1"
                $xmlTestSuite1.description | Should Be "Describe #1"
                $xmlTestSuite1.result      | Should Be "Success"
                $xmlTestSuite1.success     | Should Be "True"
                $xmlTestSuite1.time        | Should Be 1.0
    
                $xmlTestSuite2 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[1]
                $xmlTestSuite2.name        | Should Be "Describe #2"
                $xmlTestSuite2.description | Should Be "Describe #2"
                $xmlTestSuite2.result      | Should Be "Failure"
                $xmlTestSuite2.success     | Should Be "False"
                $xmlTestSuite2.time        | Should Be 2.0
            }
    
            it "should write the environment information" {
                $state = New-PesterState "."
                $testFile = "$TestDrive\Results\Tests.xml"
                Export-NunitReport $state $testFile
                $xmlResult = [xml] (Get-Content $testFile)
    
                $xmlEnvironment = $xmlResult.'test-results'.'environment'
                $xmlEnvironment.'os-Version'    | Should Not BeNullOrEmpty
                $xmlEnvironment.platform        | Should Not BeNullOrEmpty
                $xmlEnvironment.cwd             | Should Be (Get-Location).Path
                if ($env:Username) {
                    $xmlEnvironment.user        | Should Be $env:Username
                }
                $xmlEnvironment.'machine-name'  | Should Be $env:ComputerName
            }
    
            it "Should validate test results against the nunit 2.5 schema" {
                #create state
                $TestResults = New-PesterState -Path TestDrive:\
                $testResults.EnterTestGroup('Describe #1', 'Describe')
                $TestResults.AddTestResult("Successful testcase",'Passed',(New-TimeSpan -Seconds 1))
                $testResults.LeaveTestGroup('Describe #1', 'Describe')
                $testResults.EnterTestGroup('Describe #2', 'Describe')
                $TestResults.AddTestResult("Failed testcase",'Failed',(New-TimeSpan -Seconds 2))
    
                #export and validate the file
                $testFile = "$TestDrive\Results\Tests.xml"
                Export-NunitReport $testResults $testFile
                $xml = [xml] (Get-Content $testFile)
    
                $schemePath = (Get-Module -Name Pester).Path | Split-Path | Join-Path -ChildPath "nunit_schema_2.5.xsd"
                $xml.Schemas.Add($null,$schemePath) > $null
                { $xml.Validate({throw $args.Exception }) } | Should Not Throw
            }
    
            it "handles special characters in block descriptions well [email protected]#$%^&*()_+`1234567890[];'',./""- " {
                #create state
                $TestResults = New-PesterState -Path TestDrive:\
                $testResults.EnterTestGroup('Describe [email protected]#$%^&*()_+`1234567890[];'',./"- #1', 'Describe')
                $TestResults.AddTestResult("Successful testcase [email protected]#$%^&*()_+`1234567890[];'',./""-",'Passed',(New-TimeSpan -Seconds 1))
                $TestResults.LeaveTestGroup('Describe [email protected]#$%^&*()_+`1234567890[];'',./"- #1', 'Describe')
    
                #export and validate the file
                $testFile = "$TestDrive\Results\Tests.xml"
                Export-NunitReport $testResults $testFile
                $xml = [xml] (Get-Content $testFile)
    
                $schemePath = (Get-Module -Name Pester).Path | Split-Path | Join-Path -ChildPath "nunit_schema_2.5.xsd"
                $xml.Schemas.Add($null,$schemePath) > $null
                { $xml.Validate({throw $args.Exception }) } | Should Not Throw
            }
    
            Context 'Exporting Parameterized Tests (Newer format)' {
                #create state
                $TestResults = New-PesterState -Path TestDrive:\
                $testResults.EnterTestGroup('Mocked Describe', 'Describe')
    
                $TestResults.AddTestResult(
                    'Parameterized Testcase One',
                    'Passed',
                    (New-TimeSpan -Seconds 1),
                    $null,
                    $null,
                    'Parameterized Testcase <A>',
                    @{Parameter = 'One'}
                )
    
                $parameters = New-Object System.Collections.Specialized.OrderedDictionary
                $parameters.Add('StringParameter', 'Two')
                $parameters.Add('NullParameter', $null)
                $parameters.Add('NumberParameter', -42.67)
    
                $TestResults.AddTestResult(
                    'Parameterized Testcase <A>',
                    'Failed',
                    (New-TimeSpan -Seconds 1),
                    'Assert failed: "Expected: Test. But was: Testing"',
                    'at line: 28 in  C:\Pester\Result.Tests.ps1',
                    'Parameterized Testcase <A>',
                    $parameters
                )
    
                #export and validate the file
                $testFile = "$TestDrive\Results\Tests.xml"
                Export-NunitReport $testResults $testFile
                $xmlResult    = [xml] (Get-Content $testFile)
    
                It 'should write parameterized test results correctly' {
                    $xmlTestSuite = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-suite'
    
                    $xmlTestSuite.name        | Should Be 'Mocked Describe.Parameterized Testcase <A>'
                    $xmlTestSuite.description | Should Be 'Parameterized Testcase <A>'
                    $xmlTestSuite.type        | Should Be 'ParameterizedTest'
                    $xmlTestSuite.result      | Should Be 'Failure'
                    $xmlTestSuite.success     | Should Be 'False'
                    $xmlTestSuite.time        | Should Be '2'
    
                    $testCase1 = $xmlTestSuite.results.'test-case'[0]
                    $testCase2 = $xmlTestSuite.results.'test-case'[1]
    
                    $testCase1.Name | Should Be 'Mocked Describe.Parameterized Testcase One'
                    $testCase1.Time | Should Be 1
    
                    $testCase2.Name | Should Be 'Mocked Describe.Parameterized Testcase <A>("Two",null,-42.67)'
                    $testCase2.Time | Should Be 1
                }
    
                it 'Should validate test results against the nunit 2.5 schema' {
                    $schemaPath = (Get-Module -Name Pester).Path | Split-Path | Join-Path -ChildPath "nunit_schema_2.5.xsd"
                    $null = $xmlResult.Schemas.Add($null,$schemaPath)
                    { $xmlResult.Validate({throw $args.Exception }) } | Should Not Throw
                }
            }
        }
    
        Describe "Get-TestTime" {
            function Using-Culture {
                param (
                    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
                    [ScriptBlock]$ScriptBlock,
                    [System.Globalization.CultureInfo]$Culture='en-US'
                )
    
                $oldCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture
                try
                {
                    [System.Threading.Thread]::CurrentThread.CurrentCulture = $Culture
                    $ExecutionContext.InvokeCommand.InvokeScript($ScriptBlock)
                }
                finally
                {
                    [System.Threading.Thread]::CurrentThread.CurrentCulture = $oldCulture
                }
            }
    
            It "output is culture agnostic" {
                #on cs-CZ, de-DE and other systems where decimal separator is ",". value [double]3.5 is output as 3,5
                #this makes some of the tests fail, it could also leak to the nUnit report if the time was output
    
                $TestResult = New-Object -TypeName psObject -Property @{ Time = [timespan]35000000 } #3.5 seconds
    
                #using the string formatter here to know how the string will be output to screen
                $Result = { Get-TestTime -Tests $TestResult | Out-String -Stream } | Using-Culture -Culture de-DE
                $Result | Should Be "3.5"
            }
            It "Time is measured in seconds with 0,1 millisecond as lowest value" {
                $TestResult = New-Object -TypeName psObject -Property @{ Time = [timespan]1000 }
                Get-TestTime -Tests $TestResult | Should Be 0.0001
                $TestResult = New-Object -TypeName psObject -Property @{ Time = [timespan]100 }
                Get-TestTime -Tests $TestResult | Should Be 0
                $TestResult = New-Object -TypeName psObject -Property @{ Time = [timespan]1234567 }
                Get-TestTime -Tests $TestResult | Should Be 0.1235
            }
        }
    
        Describe "GetFullPath" {
            It "Resolves non existing path correctly" {
                pushd TestDrive:\
                $p = GetFullPath notexistingfile.txt
                popd
                $p | Should Be (Join-Path $TestDrive notexistingfile.txt)
            }
    
            It "Resolves existing path correctly" {
                pushd TestDrive:\
                New-Item -ItemType File -Name existingfile.txt
                $p = GetFullPath existingfile.txt
                popd
                $p | Should Be (Join-Path $TestDrive existingfile.txt)
            }
    
            It "Resolves full path correctly" {
                GetFullPath C:\Windows\System32\notepad.exe | Should Be C:\Windows\System32\notepad.exe
            }
        }
    }
    
  • tools\Functions\TestsRunningInCleanRunspace.Tests.ps1 Show
    function Invoke-PesterInJob ($ScriptBlock, [switch] $GenerateNUnitReport)
    {
        $PesterPath = Get-Module Pester | Select-Object -First 1 -ExpandProperty Path
    
        $job = Start-Job {
            param ($PesterPath, $TestDrive, $ScriptBlock, $GenerateNUnitReport)
            Import-Module $PesterPath -Force | Out-Null
            $ScriptBlock | Set-Content $TestDrive\Temp.Tests.ps1 | Out-Null
    
            $params = @{
                PassThru = $true
                Path = $TestDrive
            }
    
            if ($GenerateNUnitReport)
            {
                $params['OutputFile'] = "$TestDrive\Temp.Tests.xml"
                $params['OutputFormat'] = 'NUnitXml'
            }
    
            Invoke-Pester @params
    
        } -ArgumentList  $PesterPath, $TestDrive, $ScriptBlock, $GenerateNUnitReport
        $job | Wait-Job | Out-Null
    
        #not using Receive-Job to ignore any output to Host
        #TODO: how should this handle errors?
        #$job.Error | foreach { throw $_.Exception  }
        $job.Output
        $job.ChildJobs| foreach {
            $childJob = $_
            #$childJob.Error | foreach { throw $_.Exception }
            $childJob.Output
        }
        $job | Remove-Job
    }
    
    Describe "Tests running in clean runspace" {
        It "It - Skip and Pending tests" {
            #tests to be run in different runspace using different Pester instance
            $TestSuite = {
                Describe 'It - Skip and Pending tests' {
    
                    It "Skip without ScriptBlock" -skip
                    It "Skip with empty ScriptBlock" -skip {}
                    It "Skip with not empty ScriptBlock" -Skip {"something"}
    
                    It "Implicit pending" {}
                    It "Pending without ScriptBlock" -Pending
                    It "Pending with empty ScriptBlock" -Pending {}
                    It "Pending with not empty ScriptBlock" -Pending {"something"}
                }
            }
    
            $result = Invoke-PesterInJob -ScriptBlock $TestSuite
            $result.SkippedCount | Should Be 3
            $result.PendingCount | Should Be 4
            $result.TotalCount | Should Be 7
        }
    
        It "It - It without ScriptBlock fails" {
            #tests to be run in different runspace using different Pester instance
            $TestSuite = {
                Describe 'It without ScriptBlock fails' {
                   It "Fails whole describe"
                   It "is not run" { "but it would pass if it was run" }
    
                }
            }
    
            $result = Invoke-PesterInJob -ScriptBlock $TestSuite
            $result.PassedCount | Should Be 0
            $result.FailedCount | Should Be 1
    
            $result.TotalCount | Should Be 1
        }
    
        It "Invoke-Pester - PassThru output" {
            #tests to be run in different runspace using different Pester instance
            $TestSuite = {
                Describe 'PassThru output' {
                   it "Passes" { "pass" }
                   it "fails" { throw }
                   it "Skipped" -Skip {}
                   it "Pending" -Pending {}
                }
            }
    
            $result = Invoke-PesterInJob -ScriptBlock $TestSuite
            $result.PassedCount | Should Be 1
            $result.FailedCount | Should Be 1
            $result.SkippedCount | Should Be 1
            $result.PendingCount | Should Be 1
    
            $result.TotalCount | Should Be 4
        }
    
        It 'Produces valid NUnit output when syntax errors occur in test scripts' {
            $invalidScript = '
                Describe "Something" {
                    It "Works" {
                        $true | Should Be $true
                    }
                # Deliberately missing closing brace to trigger syntax error
            '
    
            $result = Invoke-PesterInJob -ScriptBlock $invalidScript -GenerateNUnitReport
    
            $result.FailedCount | Should Be 1
            $result.TotalCount | Should Be 1
            'TestDrive:\Temp.Tests.xml' | Should Exist
    
            $xml = [xml](Get-Content TestDrive:\Temp.Tests.xml)
    
            $xml.'test-results'.'test-suite'.results.'test-suite'.name | Should Not BeNullOrEmpty
        }
    }
    
    Describe 'Guarantee It fail on setup or teardown fail (running in clean runspace)' {
        #these tests are kinda tricky. We need to ensure few things:
        #1) failing BeforeEach will fail the test. This is easy, just put the BeforeEach in the same try catch as the invocation
        #   of It code.
        #2) failing AfterEach will fail the test. To do that we might put the AfterEach to the same try as the It code, BUT we also
        #   want to guarantee that the AfterEach will run even if the test in It will fail. For this reason the AfterEach must be triggered in
        #   a finally block. And there we are not protected by the catch clause. So we need another try in the the finally to catch teardown block
        #   error. If we fail to do that the state won't be correctly cleaned up and we can get strange errors like: "You are still in It block", when
        #   running next test. For the same reason I am putting the "ensure all tests run" tests here. otherwise you get false positives because you cannot determine
        #   if the suite failed because of the whole suite failed or just a single test failed.
    
        It 'It fails if BeforeEach fails' {
            $testSuite = {
                Describe 'Guarantee It fail on setup or teardown fail' {
                    BeforeEach {
                        throw [System.InvalidOperationException] 'test exception'
                    }
    
                    It 'It fails if BeforeEach fails' {
                        $true
                    }
                }
            }
    
            $result = Invoke-PesterInJob -ScriptBlock $testSuite
    
            $result.FailedCount | Should Be 1
            $result.TestResult[0].FailureMessage | Should Be "test exception"
        }
    
        It 'It fails if AfterEach fails' {
            $testSuite = {
                Describe 'Guarantee It fail on setup or teardown fail' {
                    It 'It fails if AfterEach fails' {
                        $true
                    }
    
                     AfterEach {
                        throw [System.InvalidOperationException] 'test exception'
                    }
                }
    
                Describe 'Make sure all the tests in the suite run' {
                    #when the previous test fails in after each and
                    It 'It is pending' -Pending {}
                }
            }
    
            $result = Invoke-PesterInJob -ScriptBlock $testSuite
    
            if ($result.PendingCount -ne 1)
            {
                throw "The test suite in separate runspace did not run to completion, it was likely terminated by an uncaught exception thrown in AfterEach."
            }
    
            $result.FailedCount | Should Be 1
            $result.TestResult[0].FailureMessage | Should Be "test exception"
        }
    }
    
  • tools\lib\gherkin.dll Show
    md5: FDFC91A1B02C3C034ACF67A330DE8A77 | sha1: 773BDEAA1B78EA85D9D5C7064A62AFD95468D6C8 | sha256: EC0DB688F37973BCAE06BE4ECA839276873158E6B2D4464B5FF1777F7DC71B80 | sha512: 674839611E756BBF4F5FBA1D1F5F1C91607CFEE7EE9F2E91080D3E858EE1250EFC187FFA0853D26EFDC50350903ED72CED37BC2903459E8D57B8DBEDE1C23E85
  • tools\LICENSE
  • tools\nunit_schema_2.5.xsd
  • tools\Pester.psd1 Show
    @{
    
    # Script module or binary module file associated with this manifest.
    ModuleToProcess = 'Pester.psm1'
    
    # Version number of this module.
    ModuleVersion = '4.0.1'
    
    # ID used to uniquely identify this module
    GUID = 'a699dea5-2c73-4616-a270-1f7abb777e71'
    
    # Author of this module
    Author = 'Pester Team'
    
    # Company or vendor of this module
    CompanyName = 'Pester'
    
    # Copyright statement for this module
    Copyright = 'Copyright (c) 2017 by Pester Team, licensed under Apache 2.0 License.'
    
    # Description of the functionality provided by this module
    Description = 'Pester provides a framework for running BDD style Tests to execute and validate PowerShell commands inside of PowerShell and offers a powerful set of Mocking Functions that allow tests to mimic and mock the functionality of any command inside of a piece of PowerShell code being tested. Pester tests can execute any command or script that is accessible to a pester test file. This can include functions, Cmdlets, Modules and scripts. Pester can be run in ad hoc style in a console or it can be integrated into the Build scripts of a Continuous Integration system.'
    
    # Minimum version of the Windows PowerShell engine required by this module
    PowerShellVersion = '2.0'
    
    # Functions to export from this module
    FunctionsToExport = @( 
        'Describe',
        'Context',
        'It',
        'Should',
        'Mock',
        'Assert-MockCalled',
        'Assert-VerifiableMocks',
        'New-Fixture',
        'Get-TestDriveItem',
        'Invoke-Pester',
        'Setup',
        'In',
        'InModuleScope',
        'Invoke-Mock',
        'BeforeEach',
        'AfterEach',
        'BeforeAll',
        'AfterAll'
        'Get-MockDynamicParameters',
        'Set-DynamicParameterVariables',
        'Set-TestInconclusive',
        'SafeGetCommand',
        'New-PesterOption',
    	'New-MockObject'
    
        # Gherkin Support:
        'Invoke-Gherkin',
        'When'
    )
    
    # # Cmdlets to export from this module
    # CmdletsToExport = '*'
    
    # Variables to export from this module
    VariablesToExport = @(
        'Path',
        'TagFilter',
        'ExcludeTagFilter',
        'TestNameFilter',
        'TestResult',
        'CurrentContext',
        'CurrentDescribe',
        'CurrentTest',
        'SessionState',
        'CommandCoverage',
        'BeforeEach',
        'AfterEach',
        'Strict'
    )
    
    # # Aliases to export from this module
    AliasesToExport = @(
        'And',
        'But',
        'Given'
        'Then'
    )
    
    
    # List of all modules packaged with this module
    # ModuleList = @()
    
    # List of all files packaged with this module
    # FileList = @()
    
    PrivateData = @{
        # PSData is module packaging and gallery metadata embedded in PrivateData
        # It's for rebuilding PowerShellGet (and PoshCode) NuGet-style packages
        # We had to do this because it's the only place we're allowed to extend the manifest
        # https://connect.microsoft.com/PowerShell/feedback/details/421837
        PSData = @{
            # The primary categorization of this module (from the TechNet Gallery tech tree).
            Category = "Scripting Techniques"
    
            # Keyword tags to help users find this module via navigations and search.
            Tags = @('powershell','unit_testing','bdd','tdd','mocking')
    
            # The web address of an icon which can be used in galleries to represent this module
            IconUri = "http://pesterbdd.com/images/Pester.png"
    
            # The web address of this module's project or support homepage.
            ProjectUri = "https://github.com/Pester/Pester"
    
            # The web address of this module's license. Points to a page that's embeddable and linkable.
            LicenseUri = "http://www.apache.org/licenses/LICENSE-2.0.html"
    
            # Release notes for this particular version of the module
            # ReleaseNotes = False
    
            # If true, the LicenseUrl points to an end-user license (not just a source license) which requires the user agreement before use.
            # RequireLicenseAcceptance = ""
    
            # Indicates this is a pre-release/testing version of the module.
            IsPrerelease = 'False'
        }
    }
    
    # HelpInfo URI of this module
    # HelpInfoURI = ''
    
    # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
    # DefaultCommandPrefix = ''
    
    }
    
  • tools\Pester.psm1 Show
    # Pester
    # Version: 4.0.1-rc
    # Changeset: 
    
    if ($PSVersionTable.PSVersion.Major -ge 3)
    {
        $script:IgnoreErrorPreference = 'Ignore'
        $outNullModule = 'Microsoft.PowerShell.Core'
        $outHostModule = 'Microsoft.PowerShell.Core'
    }
    else
    {
        $script:IgnoreErrorPreference = 'SilentlyContinue'
        $outNullModule = 'Microsoft.PowerShell.Utility'
        $outHostModule = $null
    }
    
    # Tried using $ExecutionState.InvokeCommand.GetCmdlet() here, but it does not trigger module auto-loading the way
    # Get-Command does.  Since this is at import time, before any mocks have been defined, that's probably acceptable.
    # If someone monkeys with Get-Command before they import Pester, they may break something.
    
    # The -All parameter is required when calling Get-Command to ensure that PowerShell can find the command it is
    # looking for. Otherwise, if you have modules loaded that define proxy cmdlets or that have cmdlets with the same
    # name as the safe cmdlets, Get-Command will return null.
    $safeCommandLookupParameters = @{
        CommandType = [System.Management.Automation.CommandTypes]::Cmdlet
        ErrorAction = [System.Management.Automation.ActionPreference]::Stop
    }
    
    if ($PSVersionTable.PSVersion.Major -gt 2)
    {
        $safeCommandLookupParameters['All'] = $true
    }
    
    $script:SafeCommands = @{
        'Add-Member'          = Get-Command -Name Add-Member          -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Add-Type'            = Get-Command -Name Add-Type            -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Compare-Object'      = Get-Command -Name Compare-Object      -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Export-ModuleMember' = Get-Command -Name Export-ModuleMember -Module Microsoft.PowerShell.Core       @safeCommandLookupParameters
        'ForEach-Object'      = Get-Command -Name ForEach-Object      -Module Microsoft.PowerShell.Core       @safeCommandLookupParameters
        'Format-Table'        = Get-Command -Name Format-Table        -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Get-ChildItem'       = Get-Command -Name Get-ChildItem       -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'Get-Command'         = Get-Command -Name Get-Command         -Module Microsoft.PowerShell.Core       @safeCommandLookupParameters
        'Get-Content'         = Get-Command -Name Get-Content         -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'Get-Date'            = Get-Command -Name Get-Date            -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Get-Item'            = Get-Command -Name Get-Item            -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'Get-Location'        = Get-Command -Name Get-Location        -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'Get-Member'          = Get-Command -Name Get-Member          -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Get-Module'          = Get-Command -Name Get-Module          -Module Microsoft.PowerShell.Core       @safeCommandLookupParameters
        'Get-PSDrive'         = Get-Command -Name Get-PSDrive         -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'Get-Variable'        = Get-Command -Name Get-Variable        -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Group-Object'        = Get-Command -Name Group-Object        -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Join-Path'           = Get-Command -Name Join-Path           -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'Measure-Object'      = Get-Command -Name Measure-Object      -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'New-Item'            = Get-Command -Name New-Item            -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'New-Module'          = Get-Command -Name New-Module          -Module Microsoft.PowerShell.Core       @safeCommandLookupParameters
        'New-Object'          = Get-Command -Name New-Object          -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'New-PSDrive'         = Get-Command -Name New-PSDrive         -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'New-Variable'        = Get-Command -Name New-Variable        -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Out-Host'            = Get-Command -Name Out-Host            -Module $outHostModule                  @safeCommandLookupParameters
        'Out-Null'            = Get-Command -Name Out-Null            -Module $outNullModule                  @safeCommandLookupParameters
        'Out-String'          = Get-Command -Name Out-String          -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Pop-Location'        = Get-Command -Name Pop-Location        -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'Push-Location'       = Get-Command -Name Push-Location       -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'Remove-Item'         = Get-Command -Name Remove-Item         -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'Remove-PSBreakpoint' = Get-Command -Name Remove-PSBreakpoint -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Remove-PSDrive'      = Get-Command -Name Remove-PSDrive      -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'Remove-Variable'     = Get-Command -Name Remove-Variable     -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Resolve-Path'        = Get-Command -Name Resolve-Path        -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'Select-Object'       = Get-Command -Name Select-Object       -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Set-Content'         = Get-Command -Name Set-Content         -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'Set-PSBreakpoint'    = Get-Command -Name Set-PSBreakpoint    -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Set-StrictMode'      = Get-Command -Name Set-StrictMode      -Module Microsoft.PowerShell.Core       @safeCommandLookupParameters
        'Set-Variable'        = Get-Command -Name Set-Variable        -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Sort-Object'         = Get-Command -Name Sort-Object         -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Split-Path'          = Get-Command -Name Split-Path          -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'Start-Sleep'         = Get-Command -Name Start-Sleep         -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Test-Path'           = Get-Command -Name Test-Path           -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
        'Where-Object'        = Get-Command -Name Where-Object        -Module Microsoft.PowerShell.Core       @safeCommandLookupParameters
        'Write-Error'         = Get-Command -Name Write-Error         -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    	'Write-Host'          = Get-Command -Name Write-Host          -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Write-Progress'      = Get-Command -Name Write-Progress      -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Write-Verbose'       = Get-Command -Name Write-Verbose       -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
        'Write-Warning'       = Get-Command -Name Write-Warning       -Module Microsoft.PowerShell.Utility    @safeCommandLookupParameters
    }
    
    # Not all platforms have Get-WmiObject (Nano)
    # Get-CimInstance is prefered, but we can use Get-WmiObject if it exists
    # Moreover, it shouldn't really be fatal if neither of those cmdlets
    # exist
    if ( Get-Command -ea SilentlyContinue Get-CimInstance )
    {
        $script:SafeCommands['Get-CimInstance'] = Get-Command -Name Get-CimInstance -Module CimCmdlets @safeCommandLookupParameters
    }
    elseif ( Get-command -ea SilentlyContinue Get-WmiObject )
    {
        $script:SafeCommands['Get-WmiObject']   = Get-Command -Name Get-WmiObject   -Module Microsoft.PowerShell.Management @safeCommandLookupParameters
    }
    else
    {
        Write-Warning "OS Information retrieval is not possible, reports will contain only partial system data"
    }
    
    # little sanity check to make sure we don't blow up a system with a typo up there
    # (not that I've EVER done that by, for example, mapping New-Item to Remove-Item...)
    
    foreach ($keyValuePair in $script:SafeCommands.GetEnumerator())
    {
        if ($keyValuePair.Key -ne $keyValuePair.Value.Name)
        {
            throw "SafeCommands entry for $($keyValuePair.Key) does not hold a reference to the proper command."
        }
    }
    
    $script:AssertionOperators = & $SafeCommands['New-Object'] 'Collections.Generic.Dictionary[string,object]'([StringComparer]::InvariantCultureIgnoreCase)
    $script:AssertionAliases = & $SafeCommands['New-Object'] 'Collections.Generic.Dictionary[string,object]'([StringComparer]::InvariantCultureIgnoreCase)
    $script:AssertionDynamicParams = & $SafeCommands['New-Object'] System.Management.Automation.RuntimeDefinedParameterDictionary
    
    function Test-NullOrWhiteSpace {
        param ([string]$String)
    
        $String -match "^\s*$"
    }
    
    function Assert-ValidAssertionName
    {
        param([string]$Name)
        if ($Name -notmatch '^\S+$')
        {
            throw "Assertion name '$name' is invalid, assertion name must be a single word."
        }
    }
    
    function Assert-ValidAssertionAlias
    {
        param([string]$Alias)
        if ($Alias -notmatch '^\S+$')
        {
            throw "Assertion alias '$string' is invalid, assertion alias must be a single word."
        }
    }
    
    function Add-AssertionOperator
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            [string] $Name,
    
            [Parameter(Mandatory = $true)]
            [scriptblock] $Test,
    
            [ValidateNotNullOrEmpty()]
            [string[]] $Alias,
    
            [switch] $SupportsArrayInput
        )
    
        $namesToCheck = @(
            $Name
            $Alias
        )
    
        Assert-AssertionOperatorNameIsUnique -Name $namesToCheck
    
        $entry = New-Object psobject -Property @{
            Test                       = $Test
            SupportsArrayInput         = [bool]$SupportsArrayInput
            Name                       = $Name
            Alias                      = $Alias
        }
    
        $script:AssertionOperators[$Name] = $entry
    
        foreach ($string in $Alias | where { -not (Test-NullOrWhiteSpace $_)})
        {
            Assert-ValidAssertionAlias -Alias $string
            $script:AssertionAliases[$string] = $Name
        }
    
        Add-AssertionDynamicParameterSet -AssertionEntry $entry
    }
    
    function Assert-AssertionOperatorNameIsUnique
    {
        param (
            [string[]] $Name
        )
    
        foreach ($string in $name | where { -not (Test-NullOrWhiteSpace $_)})
        {
            Assert-ValidAssertionName -Name $string
    
            if ($script:AssertionOperators.ContainsKey($string))
            {
                throw "Assertion operator name '$string' has been added multiple times."
            }
    
            if ($script:AssertionAliases.ContainsKey($string))
            {
                throw "Assertion operator name '$string' already exists as an alias for operator '$($script:AssertionAliases[$key])'"
            }
        }
    }
    
    function Add-AssertionDynamicParameterSet
    {
        param (
            [object] $AssertionEntry
        )
    
        ${function:__AssertionTest__} = $AssertionEntry.Test
        $commandInfo = Get-Command __AssertionTest__ -CommandType Function
        $metadata = [System.Management.Automation.CommandMetadata]$commandInfo
    
        $attribute = New-Object Management.Automation.ParameterAttribute
        $attribute.ParameterSetName = $AssertionEntry.Name
        $attribute.Mandatory = $true
    
        $attributeCollection = New-Object Collections.ObjectModel.Collection[Attribute]
        $null = $attributeCollection.Add($attribute)
        if (-not (Test-NullOrWhiteSpace $AssertionEntry.Alias))
        {
            Assert-ValidAssertionAlias -Alias $AssertionEntry.Alias
            $attribute = New-Object System.Management.Automation.AliasAttribute($AssertionEntry.Alias)
            $attributeCollection.Add($attribute)
        }
    
        $dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter($AssertionEntry.Name, [switch], $attributeCollection)
        $null = $script:AssertionDynamicParams.Add($AssertionEntry.Name, $dynamic)
    
        if ($script:AssertionDynamicParams.ContainsKey('Not'))
        {
            $dynamic = $script:AssertionDynamicParams['Not']
        }
        else
        {
            $dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Not', [switch], (New-Object System.Collections.ObjectModel.Collection[Attribute]))
            $null = $script:AssertionDynamicParams.Add('Not', $dynamic)
        }
    
        $attribute = New-Object System.Management.Automation.ParameterAttribute
        $attribute.ParameterSetName = $AssertionEntry.Name
        $attribute.Mandatory = $false
        $null = $dynamic.Attributes.Add($attribute)
    
        $i = 1
        foreach ($parameter in $metadata.Parameters.Values)
        {
            if ($parameter.Name -eq 'ActualValue' -or $parameter.Name -eq 'Not' -or $parameter.Name -eq 'Negate') { continue }
    
            if ($script:AssertionOperators.ContainsKey($parameter.Name) -or $script:AssertionAliases.ContainsKey($parameter.Name))
            {
                throw "Test block for assertion operator $($AssertionEntry.Name) contains a parameter named $($parameter.Name), which conflicts with another assertion operator's name or alias."
            }
    
            foreach ($alias in $parameter.Aliases)
            {
                if ($script:AssertionOperators.ContainsKey($alias) -or $script:AssertionAliases.ContainsKey($alias))
                {
                    throw "Test block for assertion operator $($AssertionEntry.Name) contains a parameter named $($parameter.Name) with alias $alias, which conflicts with another assertion operator's name or alias."
                }
            }
    
            if ($script:AssertionDynamicParams.ContainsKey($parameter.Name))
            {
                $dynamic = $script:AssertionDynamicParams[$parameter.Name]
            }
            else
               {
                # We deliberatey use a type of [object] here to avoid conflicts between different assertion operators that may use the same parameter name.
                # We also don't bother to try to copy transformation / validation attributes here for the same reason.
                # Because we'll be passing these parameters on to the actual test function later, any errors will come out at that time.
    
                $dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter($parameter.Name, [object], (New-Object System.Collections.ObjectModel.Collection[Attribute]))
                $null = $script:AssertionDynamicParams.Add($parameter.Name, $dynamic)
            }
    
            $attribute = New-Object Management.Automation.ParameterAttribute
            $attribute.ParameterSetName = $AssertionEntry.Name
            $attribute.Mandatory = $false
            $attribute.Position = ($i++)
    
            $null = $dynamic.Attributes.Add($attribute)
        }
    }
    
    function Get-AssertionOperatorEntry([string] $Name)
    {
        return $script:AssertionOperators[$Name]
    }
    
    function Get-AssertionDynamicParams
    {
        return $script:AssertionDynamicParams
    }
    
    $Script:PesterRoot = & $SafeCommands['Split-Path'] -Path $MyInvocation.MyCommand.Path
    $moduleRoot = & $script:SafeCommands['Split-Path'] -Path $MyInvocation.MyCommand.Path
    
    "$PesterRoot\Functions\*.ps1", "$PesterRoot\Functions\Assertions\*.ps1" |
    & $script:SafeCommands['Resolve-Path'] |
    & $script:SafeCommands['Where-Object'] { -not ($_.ProviderPath.ToLower().Contains(".tests.")) } |
    & $script:SafeCommands['ForEach-Object'] { . $_.ProviderPath }
    
    Add-Type -TypeDefinition @"
    using System;
    
    namespace Pester
    {
    	[Flags]
    	public enum OutputTypes
    	{
            None = 0,
            Default = 1,
            Passed = 2,
            Failed = 4,
            Pending = 8,
            Skipped = 16,
            Inconclusive = 32,
            Describe = 64,
            Context = 128,
            Summary = 256,
            Header = 512,
            All = Default | Passed | Failed | Pending | Skipped | Inconclusive | Describe | Context | Summary | Header,
            Fails = Default | Failed | Pending | Skipped | Inconclusive | Describe | Context | Summary | Header
    	}
    }
    "@
    
    function Has-Flag  {
         param
         (
             [Parameter(Mandatory = $true)]
             [Pester.OutputTypes]
             $Setting,
             [Parameter(Mandatory = $true, ValueFromPipeline=$true)]
             [Pester.OutputTypes]
             $Value
         )
    
      0 -ne ($Setting -band $Value) 
    }
    
    function Invoke-Pester {
    <#
    .SYNOPSIS
    Runs Pester tests
    
    .DESCRIPTION
    The Invoke-Pester function runs Pester tests, including *.Tests.ps1 files and 
    Pester tests in PowerShell scripts.
    
    You can run scripts that include Pester tests just as you would any other 
    Windows PowerShell script, including typing the full path at the command line 
    and running in a script editing program. Typically, you use Invoke-Pester to run 
    all Pester tests in a directory, or to use its many helpful parameters, 
    including parameters that generate custom objects or XML files.
    
    By default, Invoke-Pester runs all *.Tests.ps1 files in the current directory 
    and all subdirectories recursively. You can use its parameters to select tests 
    by file name, test name, or tag. 
    
    To run Pester tests in scripts that take parameter values, use the Script 
    parameter with a hash table value. 
    
    Also, by default, Pester tests write test results to the console host, much like 
    Write-Host does, but you can use the Quiet parameter to suppress the host 
    messages, use the PassThru parameter to generate a custom object 
    (PSCustomObject) that contains the test results, use the OutputXml and 
    OutputFormat parameters to write the test results to an XML file, and use the 
    EnableExit parameter to return an exit code that contains the number of failed 
    tests. 
    
    You can also use the Strict parameter to fail all pending and skipped tests. 
    This feature is ideal for build systems and other processes that require success 
    on every test. 
    
    To help with test design, Invoke-Pester includes a CodeCoverage parameter that 
    lists commands, functions, and lines of code that did not run during test 
    execution and returns the code that ran as a percentage of all tested code.
    
    Invoke-Pester, and the Pester module that exports it, are products of an 
    open-source project hosted on GitHub. To view, comment, or contribute to the 
    repository, see https://github.com/Pester.
    
    .PARAMETER Script
    Specifies the test files that Pester runs. You can also use the Script parameter 
    to pass parameter names and values to a script that contains Pester tests. The 
    value of the Script parameter can be a string, a hash table, or a collection 
    of hash tables and strings. Wildcard characters are supported.
    
    The Script parameter is optional. If you omit it, Invoke-Pester runs all 
    *.Tests.ps1 files in the local directory and its subdirectories recursively. 
        
    To run tests in other files, such as .ps1 files, enter the path and file name of
    the file. (The file name is required. Name patterns that end in "*.ps1" run only
    *.Tests.ps1 files.) 
    
    To run a Pester test with parameter names and/or values, use a hash table as the 
    value of the script parameter. The keys in the hash table are:
    
    -- Path [string] (required): Specifies a test to run. The value is a path\file 
       name or name pattern. Wildcards are permitted. All hash tables in a Script 
       parameter value must have a Path key. 
        
    -- Parameters [hashtable]: Runs the script with the specified parameters. The 
       value is a nested hash table with parameter name and value pairs, such as 
       @{UserName = 'User01'; Id = '28'}. 
        
    -- Arguments [array]: An array or comma-separated list of parameter values 
       without names, such as 'User01', 28. Use this key to pass values to positional 
       parameters.
        
    
    .PARAMETER TestName
    Runs only tests in Describe blocks that have the specified name or name pattern.
    Wildcard characters are supported. 
        
    If you specify multiple TestName values, Invoke-Pester runs tests that have any 
    of the values in the Describe name (it ORs the TestName values).
        
    .PARAMETER EnableExit
    Will cause Invoke-Pester to exit with a exit code equal to the number of failed 
    tests once all tests have been run. Use this to "fail" a build when any tests fail.
    
    .PARAMETER OutputFile
    The path where Invoke-Pester will save formatted test results log file. 
    If this path is not provided, no log will be generated.
    
    .PARAMETER OutputFormat
    The format of output. Two formats of output are supported: NUnitXML and
    LegacyNUnitXML.
    
    .PARAMETER Tag
    Runs only tests in Describe blocks with the specified Tag parameter values. 
    Wildcard characters and Tag values that include spaces or whitespace characters 
    are not supported.
        
    When you specify multiple Tag values, Invoke-Pester runs tests that have any 
    of the listed tags (it ORs the tags). However, when you specify TestName 
    and Tag values, Invoke-Pester runs only describe blocks that have one of the 
    specified TestName values and one of the specified Tag values.
    
    If you use both Tag and ExcludeTag, ExcludeTag takes precedence.
        
    .PARAMETER ExcludeTag
    Omits tests in Describe blocks with the specified Tag parameter values. Wildcard
    characters and Tag values that include spaces or whitespace characters are not 
    supported.
        
    When you specify multiple ExcludeTag values, Invoke-Pester omits tests that have
    any of the listed tags (it ORs the tags). However, when you specify TestName 
    and ExcludeTag values, Invoke-Pester omits only describe blocks that have one 
    of the specified TestName values and one of the specified Tag values.
    
    If you use both Tag and ExcludeTag, ExcludeTag takes precedence
    
    .PARAMETER PassThru
    Returns a custom object (PSCustomObject) that contains the test results. 
        
    By default, Invoke-Pester writes to the host program, not to the output stream (stdout). 
    If you try to save the result in a variable, the variable is empty unless you 
    use the PassThru parameter.
        
    To suppress the host output, use the Quiet parameter.
    
    .PARAMETER CodeCoverage
    Adds a code coverage report to the Pester tests. Takes strings or hash table values.
        
    A code coverage report lists the lines of code that did and did not run during 
    a Pester test. This report does not tell whether code was tested; only whether 
    the code ran during the test.
    
    By default, the code coverage report is written to the host program 
    (like Write-Host). When you use the PassThru parameter, the custom object 
    that Invoke-Pester returns has an additional CodeCoverage property that contains 
    a custom object with detailed results of the code coverage test, including lines 
    hit, lines missed, and helpful statistics.
        
    However, NUnitXML and LegacyNUnitXML output (OutputXML, OutputFormat) do not include 
    any code coverage information, because it's not supported by the schema.
        
    Enter the path to the files of code under test (not the test file). 
    Wildcard characters are supported. If you omit the path, the default is local 
    directory, not the directory specified by the Script parameter.
    
    To run a code coverage test only on selected functions or lines in a script, 
    enter a hash table value with the following keys:
        
    -- Path (P)(mandatory) <string>. Enter one path to the files. Wildcard characters
       are supported, but only one string is permitted.
    
    One of the following: Function or StartLine/EndLine
        
    -- Function (F) <string>: Enter the function name. Wildcard characters are 
       supported, but only one string is permitted.
        
    -or-
        
    -- StartLine (S): Performs code coverage analysis beginning with the specified 
       line. Default is line 1.
    -- EndLine (E): Performs code coverage analysis ending with the specified line. 
       Default is the last line of the script.
        
    
    .PARAMETER Strict
    Makes Pending and Skipped tests to Failed tests. Useful for continuous 
    integration where you need to make sure all tests passed.
    
    .PARAMETER Show
    Customizes the output Pester writes to the screen. Available options are None, Default,
    Passed, Failed, Pending, Skipped, Inconclusive, Describe, Context, Summary, Header, All, Fails.
    
    The options can be combined to define presets. 
    Common use cases are:
    
    None - to write no output to the screen.
    All - to write all available information (this is default option).
    Fails - to write everything except Passed (but including Describes etc.).
    
    A common setting is also Failed, Summary, to write only failed tests and test summary.
        
    This parameter does not affect the PassThru custom object or the XML output that
    is written when you use the Output parameters.
    
    .PARAMETER PesterOption
    Sets advanced options for the test execution. Enter a PesterOption object, 
    such as one that you create by using the New-PesterOption cmdlet, or a hash table
    in which the keys are option names and the values are option values.
    For more information on the options available, see the help for New-PesterOption.
    
    .Example
    Invoke-Pester
    
    This command runs all *.Tests.ps1 files in the current directory and its subdirectories.
    
    .Example
    Invoke-Pester -Script .\Util*
    
    This commands runs all *.Tests.ps1 files in subdirectories with names that begin
    with 'Util' and their subdirectories.
        
    .Example
    Invoke-Pester -Script D:\MyModule, @{ Path = '.\Tests\Utility\ModuleUnit.Tests.ps1'; Parameters = @{ Name = 'User01' }; Arguments = srvNano16  }
    
    This command runs all *.Tests.ps1 files in D:\MyModule and its subdirectories. 
    It also runs the tests in the ModuleUnit.Tests.ps1 file using the following
    parameters: .\Tests\Utility\ModuleUnit.Tests.ps1 srvNano16 -Name User01 
    
    .Example
    Invoke-Pester -TestName "Add Numbers"
    
    This command runs only the tests in the Describe block named "Add Numbers".
        
    .EXAMPLE
    $results = Invoke-Pester -Script D:\MyModule -PassThru -Show None
    $failed = $results.TestResult | where Result -eq 'Failed'
    
    $failed.Name
    cannot find help for parameter: Force : in Compress-Archive
    help for Force parameter in Compress-Archive has wrong Mandatory value
    help for Compress-Archive has wrong parameter type for Force
    help for Update parameter in Compress-Archive has wrong Mandatory value
    help for DestinationPath parameter in Expand-Archive has wrong Mandatory value
        
    $failed[0]
    Describe               : Test help for Compress-Archive in Microsoft.PowerShell.Archive (1.0.0.0)
    Context                : Test parameter help for Compress-Archive
    Name                   : cannot find help for parameter: Force : in Compress-Archive
    Result                 : Failed
    Passed                 : False
    Time                   : 00:00:00.0193083
    FailureMessage         : Expected: value to not be empty
    StackTrace             : at line: 279 in C:\GitHub\PesterTdd\Module.Help.Tests.ps1
                             279:                     $parameterHelp.Description.Text | Should Not BeNullOrEmpty
    ErrorRecord            : Expected: value to not be empty
    ParameterizedSuiteName :
    Parameters             : {}
        
    This examples uses the PassThru parameter to return a custom object with the 
    Pester test results. By default, Invoke-Pester writes to the host program, but not 
    to the output stream. It also uses the Quiet parameter to suppress the host output.
        
    The first command runs Invoke-Pester with the PassThru and Quiet parameters and
    saves the PassThru output in the $results variable.
        
    The second command gets only failing results and saves them in the $failed variable.
        
    The third command gets the names of the failing results. The result name is the 
    name of the It block that contains the test.
    
    The fourth command uses an array index to get the first failing result. The
    property values describe the test, the expected result, the actual result, and
    useful values, including a stack tace.
    
    .Example
    Invoke-Pester -EnableExit -OutputFile ".\artifacts\TestResults.xml" -OutputFormat NUnitXml
    
    This command runs all tests in the current directory and its subdirectories. It
    writes the results to the TestResults.xml file using the NUnitXml schema. The 
    test returns an exit code equal to the number of test failures.
    
     .EXAMPLE
    Invoke-Pester -CodeCoverage 'ScriptUnderTest.ps1'
    
    Runs all *.Tests.ps1 scripts in the current directory, and generates a coverage 
    report for all commands in the "ScriptUnderTest.ps1" file.
    
    .EXAMPLE
    Invoke-Pester -CodeCoverage @{ Path = 'ScriptUnderTest.ps1'; Function = 'FunctionUnderTest' }
    
    Runs all *.Tests.ps1 scripts in the current directory, and generates a coverage 
    report for all commands in the "FunctionUnderTest" function in the "ScriptUnderTest.ps1" file.
    
    .EXAMPLE
    Invoke-Pester -CodeCoverage @{ Path = 'ScriptUnderTest.ps1'; StartLine = 10; EndLine = 20 }
    
    Runs all *.Tests.ps1 scripts in the current directory, and generates a coverage 
    report for all commands on lines 10 through 20 in the "ScriptUnderTest.ps1" file.
    
    .EXAMPLE
    Invoke-Pester -Script C:\Tests -Tag UnitTest, Newest -ExcludeTag Bug
        
    This command runs *.Tests.ps1 files in C:\Tests and its subdirectories. In those files, it runs only tests that have UnitTest or Newest tags, unless the test also has a Bug tag.
        
    .LINK
    https://github.com/pester/Pester/wiki/Invoke-Pester
    Describe
    about_Pester
    New-PesterOption
    
    #>
        [CmdletBinding(DefaultParameterSetName = 'Default')]
        param(
            [Parameter(Position=0,Mandatory=0)]
            [Alias('Path', 'relative_path')]
            [object[]]$Script = '.',
    
            [Parameter(Position=1,Mandatory=0)]
            [Alias("Name")]
            [string[]]$TestName,
    
            [Parameter(Position=2,Mandatory=0)]
            [switch]$EnableExit,
    
            [Parameter(Position=4,Mandatory=0)]
            [Alias('Tags')]
            [string[]]$Tag,
    
            [string[]]$ExcludeTag,
    
            [switch]$PassThru,
    
            [object[]] $CodeCoverage = @(),
    
            [Switch]$Strict,
    
            [Parameter(Mandatory = $true, ParameterSetName = 'NewOutputSet')]
            [string] $OutputFile,
    
            [Parameter(ParameterSetName = 'NewOutputSet')]
            [ValidateSet('NUnitXml')]
            [string] $OutputFormat = 'NUnitXml',
    
            [Switch]$Quiet,
    
            [object]$PesterOption,
    
            [Pester.OutputTypes]$Show = 'All'
        )
        begin {
            # Ensure when running Pester that we're using RSpec strings
            Import-LocalizedData -BindingVariable Script:ReportStrings -BaseDirectory $PesterRoot -FileName RSpec.psd1
        }
    
        end {
            if ($PSBoundParameters.ContainsKey('Quiet'))
            {
                & $script:SafeCommands['Write-Warning'] 'The -Quiet parameter has been deprecated; please use the new -Show parameter instead. To get no output use -Show None.'
               & $script:SafeCommands['Start-Sleep'] -Seconds 2
            }
    
            $script:mockTable = @{}
            $pester = New-PesterState -TestNameFilter $TestName -TagFilter ($Tag -split "\s") -ExcludeTagFilter ($ExcludeTag -split "\s") -SessionState $PSCmdlet.SessionState -Strict:$Strict -Show:$Show -PesterOption $PesterOption
    		if ($Quiet)
    		{
    			$Show = [Pester.OutputTypes]::None  
    		}
    
            try
            {
                Enter-CoverageAnalysis -CodeCoverage $CodeCoverage -PesterState $pester
                Write-PesterStart $pester $Script
                
    
                $invokeTestScript = {
                    param (
                        [Parameter(Position = 0)]
                        [string] $Path,
    
                        [object[]] $Arguments = @(),
                        [System.Collections.IDictionary] $Parameters = @{}
                    )
    
                    & $Path @Parameters @Arguments
                }
    
                Set-ScriptBlockScope -ScriptBlock $invokeTestScript -SessionState $PSCmdlet.SessionState
    
                $testScripts = @(ResolveTestScripts $Script)
    
                foreach ($testScript in $testScripts)
                {
                    try
                    {
                        $pester.EnterTestGroup($testScript.Path, 'Script')
                        Write-Describe $testScript.Path -CommandUsed Script
                        do
                        {
                            & $invokeTestScript -Path $testScript.Path -Arguments $testScript.Arguments -Parameters $testScript.Parameters
                        } until ($true)
                    }
                    catch
                    {
                        $firstStackTraceLine = $_.ScriptStackTrace -split '\r?\n' | & $script:SafeCommands['Select-Object'] -First 1
                        $pester.AddTestResult("Error occurred in test script '$($testScript.Path)'", "Failed", $null, $_.Exception.Message, $firstStackTraceLine, $null, $null, $_)
    
                        # This is a hack to ensure that XML output is valid for now.  The test-suite names come from the Describe attribute of the TestResult
                        # objects, and a blank name is invalid NUnit XML.  This will go away when we promote test scripts to have their own test-suite nodes,
                        # planned for v4.0
                        $pester.TestResult[-1].Describe = "Error in $($testScript.Path)"
    
                        $pester.TestResult[-1] | Write-PesterResult
                    }
                    finally
                    {
                        Exit-MockScope
                        $pester.LeaveTestGroup($testScript.Path, 'Script')
                    }
                }
    
                $pester | Write-PesterReport
                $coverageReport = Get-CoverageReport -PesterState $pester
                Write-CoverageReport -CoverageReport $coverageReport
                Exit-CoverageAnalysis -PesterState $pester
            }
            finally
            {
                Exit-MockScope
            }
    
            Set-PesterStatistics
    
            if (& $script:SafeCommands['Get-Variable'] -Name OutputFile -ValueOnly -ErrorAction $script:IgnoreErrorPreference) {
                Export-PesterResults -PesterState $pester -Path $OutputFile -Format $OutputFormat
            }
    
        if ($PassThru) {
            #remove all runtime properties like current* and Scope
            $properties = @(
                "TagFilter","ExcludeTagFilter","TestNameFilter","TotalCount","PassedCount","FailedCount","SkippedCount","PendingCount",'InconclusiveCount',"Time","TestResult"
    
                    if ($CodeCoverage)
                    {
                        @{ Name = 'CodeCoverage'; Expression = { $coverageReport } }
                    }
                )
    
                $pester | & $script:SafeCommands['Select-Object'] -Property $properties
            }
    
            if ($EnableExit) { Exit-WithCode -FailedCount $pester.FailedCount }
        }
    }
    
    function New-PesterOption
    {
    <#
    .SYNOPSIS
       Creates an object that contains advanced options for Invoke-Pester
    .PARAMETER IncludeVSCodeMarker
       When this switch is set, an extra line of output will be written to the console for test failures, making it easier
       for VSCode's parser to provide highlighting / tooltips on the line where the error occurred.
    .PARAMETER TestSuiteName
       When generating NUnit XML output, this controls the name assigned to the root "test-suite" element.  Defaults to "Pester".
    .INPUTS
       None
       You cannot pipe input to this command.
    .OUTPUTS
       System.Management.Automation.PSObject
    .LINK
       Invoke-Pester
    #>
    
        [CmdletBinding()]
        param (
            [switch] $IncludeVSCodeMarker,
    
            [ValidateNotNullOrEmpty()]
            [string] $TestSuiteName = 'Pester'
        )
    
        return & $script:SafeCommands['New-Object'] psobject -Property @{
            IncludeVSCodeMarker = [bool]$IncludeVSCodeMarker
            TestSuiteName       = $TestSuiteName
        }
    }
    
    function ResolveTestScripts
    {
        param ([object[]] $Path)
    
        $resolvedScriptInfo = @(
            foreach ($object in $Path)
            {
                if ($object -is [System.Collections.IDictionary])
                {
                    $unresolvedPath = Get-DictionaryValueFromFirstKeyFound -Dictionary $object -Key 'Path', 'p'
                    $arguments      = @(Get-DictionaryValueFromFirstKeyFound -Dictionary $object -Key 'Arguments', 'args', 'a')
                    $parameters     = Get-DictionaryValueFromFirstKeyFound -Dictionary $object -Key 'Parameters', 'params'
    
                    if ($null -eq $Parameters) { $Parameters = @{} }
    
                    if ($unresolvedPath -isnot [string] -or $unresolvedPath -notmatch '\S')
                    {
                        throw 'When passing hashtables to the -Path parameter, the Path key is mandatory, and must contain a single string.'
                    }
    
                    if ($null -ne $parameters -and $parameters -isnot [System.Collections.IDictionary])
                    {
                        throw 'When passing hashtables to the -Path parameter, the Parameters key (if present) must be assigned an IDictionary object.'
                    }
                }
                else
                {
                    $unresolvedPath = [string] $object
                    $arguments      = @()
                    $parameters     = @{}
                }
    
                if ($unresolvedPath -notmatch '[\*\?\[\]]' -and
                    (& $script:SafeCommands['Test-Path'] -LiteralPath $unresolvedPath -PathType Leaf) -and
                    (& $script:SafeCommands['Get-Item'] -LiteralPath $unresolvedPath) -is [System.IO.FileInfo])
                {
                    $extension = [System.IO.Path]::GetExtension($unresolvedPath)
                    if ($extension -ne '.ps1')
                    {
                        & $script:SafeCommands['Write-Error'] "Script path '$unresolvedPath' is not a ps1 file."
                    }
                    else
                    {
                        & $script:SafeCommands['New-Object'] psobject -Property @{
                            Path       = $unresolvedPath
                            Arguments  = $arguments
                            Parameters = $parameters
                        }
                    }
                }
                else
                {
                    # World's longest pipeline?
    
                    & $script:SafeCommands['Resolve-Path'] -Path $unresolvedPath |
                    & $script:SafeCommands['Where-Object'] { $_.Provider.Name -eq 'FileSystem' } |
                    & $script:SafeCommands['Select-Object'] -ExpandProperty ProviderPath |
                    & $script:SafeCommands['Get-ChildItem'] -Include *.Tests.ps1 -Recurse |
                    & $script:SafeCommands['Where-Object'] { -not $_.PSIsContainer } |
                    & $script:SafeCommands['Select-Object'] -ExpandProperty FullName -Unique |
                    & $script:SafeCommands['ForEach-Object'] {
                        & $script:SafeCommands['New-Object'] psobject -Property @{
                            Path       = $_
                            Arguments  = $arguments
                            Parameters = $parameters
                        }
                    }
                }
            }
        )
    
        # Here, we have the option of trying to weed out duplicate file paths that also contain identical
        # Parameters / Arguments.  However, we already make sure that each object in $Path didn't produce
        # any duplicate file paths, and if the caller happens to pass in a set of parameters that produce
        # dupes, maybe that's not our problem.  For now, just return what we found.
    
        $resolvedScriptInfo
    }
    
    function Get-DictionaryValueFromFirstKeyFound
    {
        param ([System.Collections.IDictionary] $Dictionary, [object[]] $Key)
    
        foreach ($keyToTry in $Key)
        {
            if ($Dictionary.Contains($keyToTry)) { return $Dictionary[$keyToTry] }
        }
    }
    
    function Set-ScriptBlockScope
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            [scriptblock]
            $ScriptBlock,
    
            [Parameter(Mandatory = $true, ParameterSetName = 'FromSessionState')]
            [System.Management.Automation.SessionState]
            $SessionState,
    
            [Parameter(Mandatory = $true, ParameterSetName = 'FromSessionStateInternal')]
            $SessionStateInternal
        )
    
        $flags = [System.Reflection.BindingFlags]'Instance,NonPublic'
    
        if ($PSCmdlet.ParameterSetName -eq 'FromSessionState')
        {
            $SessionStateInternal = $SessionState.GetType().GetProperty('Internal', $flags).GetValue($SessionState, $null)
        }
    
        [scriptblock].GetProperty('SessionStateInternal', $flags).SetValue($ScriptBlock, $SessionStateInternal, $null)
    }
    
    function Get-ScriptBlockScope
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            [scriptblock]
            $ScriptBlock
        )
    
        $flags = [System.Reflection.BindingFlags]'Instance,NonPublic'
        [scriptblock].GetProperty('SessionStateInternal', $flags).GetValue($ScriptBlock, $null)
    }
    
    function Get-OperatingSystem
    {
        [CmdletBinding()]
        param()
    
        ## Prior to v6, PowerShell was solely Windows. In v6, the $IsWindows var was introduced.
        if ($PSVersionTable.PSVersion.Major -lt 6 -or $IsWindows)
        {
            'Windows'
        } 
        elseif ($IsOSX)
        {
            'OSX'
        }
        elseif ($IsLinux)
        {
            'Linux'
        }
    }
    
    function Get-TempDirectory
    {
        [CmdletBinding()]
        param()
    
        if ((Get-OperatingSystem) -eq 'Windows')
        {
            $env:TEMP
        }
        else
        {
            '/tmp'
        }
    }
    
    function SafeGetCommand
    {
        <#
            .SYNOPSIS
            This command is used by Pester's Mocking framework.  You do not need to call it directly.
        #>
    
        return $script:SafeCommands['Get-Command']
    }
    
    function Set-PesterStatistics($Node)
    {
        if ($null -eq $Node) { $Node = $pester.TestActions }
    
        foreach ($action in $Node.Actions)
        {
            if ($action.Type -eq 'TestGroup')
            {
                Set-PesterStatistics -Node $action
    
                $Node.TotalCount        += $action.TotalCount
                $Node.Time              += $action.Time
                $Node.PassedCount       += $action.PassedCount
                $Node.FailedCount       += $action.FailedCount
                $Node.SkippedCount      += $action.SkippedCount
                $Node.PendingCount      += $action.PendingCount
                $Node.InconclusiveCount += $action.InconclusiveCount
            }
            elseif ($action.Type -eq 'TestCase')
            {
                $node.TotalCount++
    
                switch ($action.Result)
                {
                    Passed       { $Node.PassedCount++;       break; }
                    Failed       { $Node.FailedCount++;       break; }
                    Skipped      { $Node.SkippedCount++;      break; }
                    Pending      { $Node.PendingCount++;      break; }
                    Inconclusive { $Node.InconclusiveCount++; break; }
                }
    
                $Node.Time += $action.Time
            }
        }
    }
    
    $snippetsDirectoryPath = "$PSScriptRoot\Snippets"
    if ((& $script:SafeCommands['Test-Path'] -Path Variable:\psise) -and
        ($null -ne $psISE) -and
        ($PSVersionTable.PSVersion.Major -ge 3) -and
        (& $script:SafeCommands['Test-Path'] $snippetsDirectoryPath))
    {
        Import-IseSnippet -Path $snippetsDirectoryPath
    }
    
    & $script:SafeCommands['Export-ModuleMember'] Describe, Context, It, In, Mock, Assert-VerifiableMocks, Assert-MockCalled, Set-TestInconclusive
    & $script:SafeCommands['Export-ModuleMember'] New-Fixture, Get-TestDriveItem, Should, Invoke-Pester, Setup, InModuleScope, Invoke-Mock
    & $script:SafeCommands['Export-ModuleMember'] BeforeEach, AfterEach, BeforeAll, AfterAll
    & $script:SafeCommands['Export-ModuleMember'] Get-MockDynamicParameters, Set-DynamicParameterVariables
    & $script:SafeCommands['Export-ModuleMember'] SafeGetCommand, New-PesterOption
    & $script:SafeCommands['Export-ModuleMember'] Invoke-Gherkin, When -Alias And, But, Given, Then
    & $script:SafeCommands['Export-ModuleMember'] New-MockObject
    
  • tools\Pester.Tests.ps1 Show
    $here = Split-Path -Parent $MyInvocation.MyCommand.Path
    
    $manifestPath   = "$here\Pester.psd1"
    $changeLogPath = "$here\CHANGELOG.md"
    
    # DO NOT CHANGE THIS TAG NAME; IT AFFECTS THE CI BUILD.
    
    Describe -Tags 'VersionChecks' "Pester manifest and changelog" {
        $script:manifest = $null
        It "has a valid manifest" {
            {
                $script:manifest = Test-ModuleManifest -Path $manifestPath -ErrorAction Stop -WarningAction SilentlyContinue
            } | Should Not Throw
        }
    
        It "has a valid name in the manifest" {
            $script:manifest.Name | Should Be Pester
        }
    
        It "has a valid guid in the manifest" {
            $script:manifest.Guid | Should Be 'a699dea5-2c73-4616-a270-1f7abb777e71'
        }
    
        It "has a valid version in the manifest" {
            $script:manifest.Version -as [Version] | Should Not BeNullOrEmpty
        }
    
        $script:changelogVersion = $null
        It "has a valid version in the changelog" {
    
            foreach ($line in (Get-Content $changeLogPath))
            {
                if ($line -match "^\D*(?<Version>(\d+\.){1,3}\d+)")
                {
                    $script:changelogVersion = $matches.Version
                    break
                }
            }
            $script:changelogVersion                | Should Not BeNullOrEmpty
            $script:changelogVersion -as [Version]  | Should Not BeNullOrEmpty
        }
    
        It "changelog and manifest versions are the same" {
            $script:changelogVersion -as [Version] | Should be ( $script:manifest.Version -as [Version] )
        }
    
        if (Get-Command git.exe -ErrorAction SilentlyContinue)
        {
            $skipVersionTest = -not [bool]((git remote -v 2>&1) -match "github.com/Pester/")
            $script:tagVersion = $null
            It "is tagged with a valid version" -skip:$skipVersionTest {
                $thisCommit = git.exe log --decorate --oneline HEAD~1..HEAD
    
                if ($thisCommit -match 'tag:\s*(\d+(?:\.\d+)*)')
                {
                    $script:tagVersion = $matches[1]
                }
    
                $script:tagVersion                  | Should Not BeNullOrEmpty
                $script:tagVersion -as [Version]    | Should Not BeNullOrEmpty
            }
    
            It "all versions are the same" -skip:$skipVersionTest {
                $script:changelogVersion -as [Version] | Should be ( $script:manifest.Version -as [Version] )
                $script:manifest.Version -as [Version] | Should be ( $script:tagVersion -as [Version] )
            }
    
        }
    }
    
    if ($PSVersionTable.PSVersion.Major -ge 3)
    {
        $error.Clear()
        Describe 'Clean treatment of the $error variable' {
            Context 'A Context' {
                It 'Performs a successful test' {
                    $true | Should Be $true
                }
            }
    
            It 'Did not add anything to the $error variable' {
                $error.Count | Should Be 0
            }
        }
    
        InModuleScope Pester {
            Describe 'SafeCommands table' {
                $path = $ExecutionContext.SessionState.Module.ModuleBase
                $filesToCheck = Get-ChildItem -Path $path -Recurse -Include *.ps1,*.psm1 -Exclude *.Tests.ps1
                $callsToSafeCommands = @(
                    foreach ($file in $files)
                    {
                        $tokens = $parseErrors = $null
                        $ast = [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref] $tokens, [ref] $parseErrors)
                        $filter = {
                            $args[0] -is [System.Management.Automation.Language.CommandAst] -and
                            $args[0].InvocationOperator -eq [System.Management.Automation.Language.TokenKind]::Ampersand -and
                            $args[0].CommandElements[0] -is [System.Management.Automation.Language.IndexExpressionAst] -and
                            $args[0].CommandElements[0].Target -is [System.Management.Automation.Language.VariableExpressionAst] -and
                            $args[0].CommandElements[0].Target.VariablePath.UserPath -match '^(?:script:)?SafeCommands$'
                        }
    
                        $ast.FindAll($filter, $true)
                    }
                )
    
                $uniqueSafeCommands = $callsToSafeCommands | ForEach-Object { $_.CommandElements[0].Index.Value } | Select-Object -Unique
    
                $missingSafeCommands = $uniqueSafeCommands | Where-Object { -not $script:SafeCommands.ContainsKey($_) }
    
                It 'The SafeCommands table contains all commands that are called from the module' {
                    $missingSafeCommands | Should Be $null
                }
            }
        }
    }
    
    Describe 'Style rules' {
        $pesterRoot = (Get-Module Pester).ModuleBase
    
        $files = @(
            Get-ChildItem $pesterRoot -Include *.ps1,*.psm1
            Get-ChildItem $pesterRoot\Functions -Include *.ps1,*.psm1 -Recurse
        )
    
        It 'Pester source files contain no trailing whitespace' {
            $badLines = @(
                foreach ($file in $files)
                {
                    $lines = [System.IO.File]::ReadAllLines($file.FullName)
                    $lineCount = $lines.Count
    
                    for ($i = 0; $i -lt $lineCount; $i++)
                    {
                        if ($lines[$i] -match '\s+$')
                        {
                            'File: {0}, Line: {1}' -f $file.FullName, ($i + 1)
                        }
                    }
                }
            )
    
            if ($badLines.Count -gt 0)
            {
                throw "The following $($badLines.Count) lines contain trailing whitespace: `r`n`r`n$($badLines -join "`r`n")"
            }
        }
    
        It 'Pester Source Files all end with a newline' {
            $badFiles = @(
                foreach ($file in $files)
                {
                    $string = [System.IO.File]::ReadAllText($file.FullName)
                    if ($string.Length -gt 0 -and $string[-1] -ne "`n")
                    {
                        $file.FullName
                    }
                }
            )
    
            if ($badFiles.Count -gt 0)
            {
                throw "The following files do not end with a newline: `r`n`r`n$($badFiles -join "`r`n")"
            }
        }
    }
    
    InModuleScope Pester {
        Describe 'ResolveTestScripts' {
            Setup -File SomeFile.ps1
            Setup -File SomeFile.Tests.ps1
            Setup -File SomeOtherFile.ps1
            Setup -File SomeOtherFile.Tests.ps1
    
            It 'Resolves non-wildcarded file paths regardless of whether the file ends with Tests.ps1' {
                $result = @(ResolveTestScripts $TestDrive\SomeOtherFile.ps1)
                $result.Count | Should Be 1
                $result[0].Path | Should Be "$TestDrive\SomeOtherFile.ps1"
            }
    
            It 'Finds only *.Tests.ps1 files when the path contains wildcards' {
                $result = @(ResolveTestScripts $TestDrive\*.ps1)
                $result.Count | Should Be 2
    
                $paths = $result | Select-Object -ExpandProperty Path
    
                ($paths -contains "$TestDrive\SomeFile.Tests.ps1") | Should Be $true
                ($paths -contains "$TestDrive\SomeOtherFile.Tests.ps1") | Should Be $true
            }
    
            It 'Finds only *.Tests.ps1 files when the path refers to a directory and does not contain wildcards' {
                $result = @(ResolveTestScripts $TestDrive)
    
                $result.Count | Should Be 2
    
                $paths = $result | Select-Object -ExpandProperty Path
    
                ($paths -contains "$TestDrive\SomeFile.Tests.ps1") | Should Be $true
                ($paths -contains "$TestDrive\SomeOtherFile.Tests.ps1") | Should Be $true
            }
    
            It 'Assigns empty array and hashtable to the Arguments and Parameters properties when none are specified by the caller' {
                $result = @(ResolveTestScripts "$TestDrive\SomeFile.ps1")
    
                $result.Count | Should Be 1
                $result[0].Path | Should Be "$TestDrive\SomeFile.ps1"
    
                ,$result[0].Arguments | Should Not Be $null
                ,$result[0].Parameters | Should Not Be $null
    
                $result[0].Arguments.GetType() | Should Be ([object[]])
                $result[0].Arguments.Count | Should Be 0
    
                $result[0].Parameters.GetType() | Should Be ([hashtable])
                $result[0].Parameters.PSBase.Count | Should Be 0
            }
    
            Context 'Passing in Dictionaries instead of Strings' {
                It 'Allows the use of a "P" key instead of "Path"' {
                    $result = @(ResolveTestScripts @{ P = "$TestDrive\SomeFile.ps1" })
    
                    $result.Count | Should Be 1
                    $result[0].Path | Should Be "$TestDrive\SomeFile.ps1"
                }
    
                $testArgs = @('I am a string')
                It 'Allows the use of an "Arguments" key in the dictionary' {
                    $result = @(ResolveTestScripts @{ Path = "$TestDrive\SomeFile.ps1"; Arguments = $testArgs })
    
                    $result.Count | Should Be 1
                    $result[0].Path | Should Be "$TestDrive\SomeFile.ps1"
    
                    $result[0].Arguments.Count | Should Be 1
                    $result[0].Arguments[0] | Should Be 'I am a string'
                }
    
                It 'Allows the use of an "Args" key in the dictionary' {
                    $result = @(ResolveTestScripts @{ Path = "$TestDrive\SomeFile.ps1"; Args = $testArgs })
    
                    $result.Count | Should Be 1
                    $result[0].Path | Should Be "$TestDrive\SomeFile.ps1"
    
                    $result[0].Arguments.Count | Should Be 1
                    $result[0].Arguments[0] | Should Be 'I am a string'
                }
    
                It 'Allows the use of an "A" key in the dictionary' {
                    $result = @(ResolveTestScripts @{ Path = "$TestDrive\SomeFile.ps1"; A = $testArgs })
    
                    $result.Count | Should Be 1
                    $result[0].Path | Should Be "$TestDrive\SomeFile.ps1"
    
                    $result[0].Arguments.Count | Should Be 1
                    $result[0].Arguments[0] | Should Be 'I am a string'
                }
    
                $testParams = @{ MyKey = 'MyValue' }
                It 'Allows the use of a "Parameters" key in the dictionary' {
                    $result = @(ResolveTestScripts @{ Path = "$TestDrive\SomeFile.ps1"; Parameters = $testParams })
    
                    $result.Count | Should Be 1
                    $result[0].Path | Should Be "$TestDrive\SomeFile.ps1"
    
                    $result[0].Parameters.PSBase.Count | Should Be 1
                    $result[0].Parameters['MyKey'] | Should Be 'MyValue'
                }
    
                It 'Allows the use of a "Params" key in the dictionary' {
                    $result = @(ResolveTestScripts @{ Path = "$TestDrive\SomeFile.ps1"; Params = $testParams })
    
                    $result.Count | Should Be 1
                    $result[0].Path | Should Be "$TestDrive\SomeFile.ps1"
    
                    $result[0].Parameters.PSBase.Count | Should Be 1
                    $result[0].Parameters['MyKey'] | Should Be 'MyValue'
                }
    
                It 'Throws an error if no Path is specified' {
                    { ResolveTestScripts @{} } | Should Throw
                }
    
                It 'Throws an error if a Parameters key is used, but does not contain an IDictionary object' {
                    { ResolveTestScripts @{ P='P'; Params = 'A string' } } | Should Throw
                }
            }
        }
    
        describe 'Get-OperatingSystem' {
    
            it 'returns Windows' {
    
                Get-OperatingSystem | should be 'Windows'
            }
        }
    
        describe 'Get-TempDirectory' {
    
            it 'returns the correct temp directory for Windows' {
                mock 'Get-OperatingSystem' {
                    'Windows'
                }
                Get-TempDirectory | should be $env:TEMP
            }
    
            it 'returns the correct temp directory for MacOS' {
                mock 'Get-OperatingSystem' {
                    'MacOS'
                }
                Get-TempDirectory | should be '/tmp'
            }
    
            it 'returns the correct temp directory for Linux' {
                mock 'Get-OperatingSystem' {
                    'Linux'
                }
                Get-TempDirectory | should be '/tmp'
            }
        }
    }
    
  • tools\Snippets\Context.snippets.ps1xml
  • tools\Snippets\Describe.snippets.ps1xml
  • tools\Snippets\It.snippets.ps1xml
  • tools\Snippets\ShouldBe.snippets.ps1xml
  • tools\Snippets\ShouldBeGreaterThan.snippets.ps1xml
  • tools\Snippets\ShouldBeLessThan.snippets.ps1xml
  • tools\Snippets\ShouldBeNullOrEmpty.snippets.ps1xml
  • tools\Snippets\ShouldContain.snippets.ps1xml
  • tools\Snippets\ShouldExist.snippets.ps1xml
  • tools\Snippets\ShouldMatch.snippets.ps1xml
  • tools\Snippets\ShouldNotBe.snippets.ps1xml
  • tools\Snippets\ShouldNotBeNullOrEmpty.snippets.ps1xml
  • tools\Snippets\ShouldNotContain.snippets.ps1xml
  • tools\Snippets\ShouldNotExist.snippets.ps1xml
  • tools\Snippets\ShouldNotMatch.snippets.ps1xml
  • tools\Snippets\ShouldNotThrow.snippets.ps1xml
  • tools\Snippets\ShouldThrow.snippets.ps1xml

Virus Scan Results

In cases where actual malware is found, the packages are subject to removal. Software sometimes has false positives. Moderators do not necessarily validate the safety of the underlying software, only that a package retrieves software from the official distribution point and/or validate embedded software against official distribution point (where distribution rights allow redistribution).

Chocolatey Pro provides runtime protection from possible malware.

Dependencies

This package has no dependencies.

Package Maintainer(s)

Software Author(s)

  • Pester Team

Tags

Version History

Version Downloads Last updated Status
Pester 4.8.0 879 Wednesday, May 1, 2019 approved
Pester 4.7.3 3250 Saturday, March 23, 2019 approved
Pester 4.7.2 1189 Friday, March 8, 2019 approved
Pester 4.7.1 416 Tuesday, March 5, 2019 approved
Pester 4.7.0 237 Sunday, March 3, 2019 approved
Pester 4.4.1 8769 Thursday, September 20, 2018 approved
Pester 4.4.0 4744 Friday, July 20, 2018 approved
Pester 4.4.0-beta2 88 Sunday, July 8, 2018 approved
Pester 4.4.0-beta 204 Sunday, May 6, 2018 approved
Show More

Discussion for the Pester Package

Ground rules:

  • This discussion is only about Pester and the Pester package. If you have feedback for Chocolatey, please contact the google group.
  • This discussion will carry over multiple versions. If you have a comment about a particular version, please note that in your comments.
  • The maintainers of this Chocolatey Package will be notified about new comments that are posted to this Disqus thread, however, it is NOT a guarantee that you will get a response. If you do not hear back from the maintainers after posting a message below, please follow up by using the link on the left side of this page or follow this link to contact maintainers. If you still hear nothing back, please follow the package triage process.
  • Tell us what you love about the package or Pester, or tell us what needs improvement.
  • Share your experiences with the package, or extra configuration or gotchas that you've found.
  • If you use a url, the comment will be flagged for moderation until you've been whitelisted. Disqus moderated comments are approved on a weekly schedule if not sooner. It could take between 1-5 days for your comment to show up.

comments powered by Disqus
Chocolatey.org uses cookies to enhance the user experience of the site.
Ok