3

Edit

The crux of the question is: how do I get access to variable(s) declared in a BeforeDiscovery block in my It blocks that are not passed through by the it -foreach $var construct?


I'm having difficulties adjusting to the Discovery/Run phase and Scoping of Variables in Pester 5

Background

We are moving servers and what I'm trying to test is

  1. that every share on serverA also exists on serverB.
  2. that every readable share on serverA is also readable on serverB.

Using Pester 5, below code runs as intented but to make it work, I have to retrieve the $toShares twice. Retrieving the shares in my actual tests is using a net view and is a fairly long running operaton.

  1. I have to retrieve $toShares in the Discovery phase to construct the $readableFromShares list
  2. I have to retrieve an identical $toShares in a BeforeAll block to have them available in the should exists test

Question

How can I best restructure my test so that I only need to retrieve the $toShares once?

Testcode

$PesterPreference = [PesterConfiguration]::Default
$PesterPreference.Output.Verbosity = 'Detailed'

function Test-Path {$True} # hide the real Test-Path function
Describe "Describe" -ForEach @(
    @{fromServer ='serverA'; toServer = 'serverB'}    
    @{fromServer ='serverC'; toServer = 'serverD'}    
    ){
    
    BeforeDiscovery {
        $fromShares = 'share1', 'share2', 'share3'
        # $toShares is needed here to construct the readableFromShares array
        $toShares = 'share1', 'share2'
        $readableFromShares = $fromShares | 
            Where-Object {$toShares -contains $_} | 
            Where-Object {Test-Path "\\$($fromServer)\$($_)"}
    }
     
    Context "<fromServer> samba shares should exist on <toServer>" { 
        BeforeAll {
            # the "same" $toShares is needed here to be available in the exist tests
            $toShares = 'share1', 'share2'
        }
        It "Does \\<toServer>\<_> exist" -ForEach $fromShares {
            $toShares -contains $_ | Should -Be $true
        }
    }

    Context "Readable <fromServer> samba shares should als be readable on <toServer>" {
        It "<_> is readable on <fromServer>. \\<toServer>\<_> should also be readable." -ForEach $readableFromShares {
            Test-Path "\\$($toServer)\$($_)"| Should -Be $True
        }
    }
}

Output including two deliberate failing tests

Output testcases


Edit

Testcase including

  1. Two from/to servers (io one)
  2. Different sharenames for each set of servers

Test

$PesterPreference = [PesterConfiguration]::Default
$PesterPreference.Output.Verbosity = 'Detailed'
function Test-Path {$True} # hides the real Test-Path
function Get-FromShares($fromServer) {if ($fromServer -eq 'serverA') { @('ABshare1', 'ABshare2', 'ABshare3') } else {@('XYshare1', 'XYshare2', 'XYshare3')}}
function Get-ToShares($toServer) {if ($toServer -eq 'serverB') { @('ABshare1', 'ABshare2') } else {@('XYshare1', 'XYshare2')}}

class Shares { static $toShares = @{} }
function Test-Path {$True} # hides the real Test-Path
Describe "Describe" -ForEach @(
    @{fromServer ='serverA'; toServer = 'serverB'}    
    @{fromServer ='serverX'; toServer = 'serverY'}    
    ){

    #It "Define shares" -TestCases @( 1 )  { class Shares { static [string[]]$toShares = @('share1', 'share2') } }
    
    BeforeDiscovery {
        $fromShares = Get-FromShares($fromServer)
        [Shares]::toShares =@{ $fromServer = Get-ToShares($toServer)}
        $toShares = [Shares]::toShares[$fromServer]
        $readableFromShares = $fromShares | 
            Where-Object {$toShares -contains $_} | 
            Where-Object {Test-Path "\\$($fromServer)\$($_)"}
    }
     
    Context "<fromServer> samba shares should exist on <toServer>" { 
        BeforeAll {            
            $toShares = [Shares]::toShares[$fromServer]
        }
        It "Does \\<toServer>\<_> exist" -ForEach $fromShares {
            $toShares -contains $_ | Should -Be $true
        }
    }

    Context "Readable <fromServer> samba shares should als be readable on <toServer>" {
        It "<_> is readable on <fromServer>. \\<toServer>\<_> should also be readable." -ForEach $readableFromShares {
            Test-Path "\\$($toServer)\$($_)"| Should -Be $True
        }
    }
} 
Lieven Keersmaekers
  • 57,207
  • 13
  • 112
  • 146
  • On your test environnement, are you using PowerShell 5 or above ? In that case, why not a `class` with a `static` toShare member ? – Zilog80 Mar 29 '21 at 08:51
  • @Zilog80 - I'm using `5.1.17763.1490`. I could use a class but I don't see how that would solve my scoping issue? – Lieven Keersmaekers Mar 29 '21 at 09:19
  • I thought to something ike `BeforeAll { class Shares { static [string[]]$toShares = @('share1', 'share2') } } function Test-Path ... BeforeDiscovery { ... $toShares = [Shares]::toShares ...` – Zilog80 Mar 29 '21 at 09:48
  • I guess you can set the static member in BeforeDiscovery too and reuse it in the following scope, like `BeforeAll { class Shares { static [string[]]$toShares = @() } } function Test-Path ... BeforeDiscovery { ... [Shares]::toShares = @('share1', 'share2') ... Context " ... BeforeAll { $toShares = [Shares]::toShares ... ` – Zilog80 Mar 29 '21 at 10:08
  • @Zilog80 - I can't get it to work. I have edited my question to include your suggestion but it returns `System.Management.Automation.RuntimeException: Unable to find type [Shares].` – Lieven Keersmaekers Mar 29 '21 at 12:41
  • I check the documentation : *BeforeAll function runs. It saves the ScriptBlock provided to it, **but does not execute it yet.*** and then *Describe function runs. **It invokes** the ScriptBlock provided to it,*. Remove the `BeforeAll` statement and replace `BeforeDiscovery { ... $toShares = [Shares]::toShares` with `BeforeDiscovery { ... class Shares { static [string[]]$toShares = @('share1', 'share2') } $toShares = [Shares]::toShares`. The class should then exists at describe and run time. – Zilog80 Mar 29 '21 at 13:40
  • @Zilog80 - No that, doesn't work either. If you have Powershell running, you could just `install-module pester` and try it out. There's undoubtly logic to it, but I can't grasp it. I understand the logic behind their new 2 phase approach but it seems that this use case was easy in the past and not (easily) possible anymore. – Lieven Keersmaekers Mar 29 '21 at 16:31
  • 1
    Installing it. There surely a way ^^ – Zilog80 Mar 29 '21 at 16:33
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/230509/discussion-between-zilog80-and-lieven-keersmaekers). – Zilog80 Mar 29 '21 at 16:42

2 Answers2

3

The solution is to enrich the variables passed to the -foreach clauses with whatever data you like to have available in the It blocks.

In below example, the $fromShares array now contains an array of objects instead of a plain array of strings. Each object still contains the share name and also contains the $toShares array you need in your test.


Describe "Describe" -ForEach @(
    @{fromServer ='serverA'; toServer = 'serverB'}    
    @{fromServer ='serverX'; toServer = 'serverY'}    
    ){
    
    BeforeDiscovery {
        $toShares = Get-ToShares($toServer)
        $fromShares = Get-FromShares($fromServer) | % {
            [PSCustomObject]@{
                fromShare = $_
                toShares = $toShares
            }
        }

        $readableFromShares = $fromShares | 
            Where-Object {$toShares -contains $_} | 
            Where-Object {Test-Path "\\$($fromServer)\$($_)"}
    }
     
    Context "<fromServer> samba shares should exist on <toServer>" { 
        BeforeAll {            
            $toShares = [Shares]::toShares[$fromServer]
        }
        It "Does \\<toServer>\<_.fromShare> exist" -ForEach $fromShares {
            $_.toShares -contains $_.fromShare | Should -Be $true
        }
    }

    Context "Readable <fromServer> samba shares should als be readable on <toServer>" {
        It "<_> is readable on <fromServer>. \\<toServer>\<_> should also be readable." -ForEach $readableFromShares {
            Test-Path "\\$($toServer)\$($_)"| Should -Be $True            
        }
    }
} 
Lieven Keersmaekers
  • 57,207
  • 13
  • 112
  • 146
Zilog80
  • 2,534
  • 2
  • 15
  • 20
1

I'm not familiar with pester, but I do have some suggestions:

You may have better luck (or speed) using Get-CimInstance to list the shares from one location. Here's non-pester code for that:

# Discovery
$shares = $ServerNames | Foreach { Get-CimInstance –Class Win32_Share –ComputerName $_ }

# Run
# simple compare of the shares list between servers
$diff = Compare-Object -Property Name `
    -ReferenceObject  ($shares|where PSComputerName -Like $ServerA) `
    -DifferenceObject ($shares|where PSComputerName -Like $ServerB) `

# $diff should -eq $null

And to test if they're all readable, use the same $shares something like this:

foreach ($share in $shares) {
    It "\\$($share.PSComputerName)\$($share.Name) should be readable" {
        (Test-Path "\\$($share.PSComputerName)\$($share.Name)" -ea SilentlyContinue) | 
            Should -Be $True    
    }
}
Cpt.Whale
  • 4,784
  • 1
  • 10
  • 16
  • Get-CimInstance works for our Windows servers but not for shares provided through Samba (hp-unix servers). I get `Get-CimInstance : WinRM cannot process the request. The following error occurred while using Kerberos authentication`. If I can get it to work and if it's faster, I'll definitely use it but the crux of my question is more on how to correctly scope the variables in Pester 5. I'll edit the question to make that more clear. – Lieven Keersmaekers Mar 26 '21 at 06:58
  • @LievenKeersmaekers ah, yep it is windows-only. You may want to check out using nmap, as it lists the shares and access in the same scan: https://nmap.org/nsedoc/scripts/smb-enum-shares.html – Cpt.Whale Mar 26 '21 at 17:59
  • That's certainly out of the box thinking :). It's an option and it would solve listing the shares twice but I'd still have to write something to compare the `from` with the `to` situation. I'm not so much looking for another tool but more looking into learning Pester 5. My mental modal at the moment is incomplete (to say the least ;)) – Lieven Keersmaekers Mar 26 '21 at 18:17