Test-Requirement rewrite

test-requirement
Chris Kuech 2019-09-21 10:27:28 -07:00
parent e8bbc5267e
commit 7cc7ba3880
12 changed files with 229 additions and 219 deletions

View File

@ -4,45 +4,59 @@ $ErrorActionPreference = "Stop"
# idempotently applies a requirement
function applyRequirement([Requirement]$Requirement) {
$result = $false
if ($Requirement.Test) {
[RequirementEvent]::new($Requirement, "Test", "Start")
$result = &$Requirement.Test
[RequirementEvent]::new($Requirement, "Test", "Stop", $result)
$result = $false
if ($Requirement.Test) {
[RequirementEvent]::new($Requirement, "Test", "Start")
$result = &$Requirement.Test
[RequirementEvent]::new($Requirement, "Test", "Stop", $result)
}
if (-not $result) {
if ($Requirement.Set) {
[RequirementEvent]::new($Requirement, "Set", "Start")
$result = &$Requirement.Set
[RequirementEvent]::new($Requirement, "Set", "Stop", $result)
}
if (-not $result) {
if ($Requirement.Set) {
[RequirementEvent]::new($Requirement, "Set", "Start")
$result = &$Requirement.Set
[RequirementEvent]::new($Requirement, "Set", "Stop", $result)
}
if ($Requirement.Test -and $Requirement.Set) {
[RequirementEvent]::new($Requirement, "Validate", "Start")
$result = &$Requirement.Test
[RequirementEvent]::new($Requirement, "Validate", "Stop", $result)
if (-not $result) {
Write-Error "Failed to apply Requirement '$($Requirement.Name)'"
}
}
if ($Requirement.Test -and $Requirement.Set) {
[RequirementEvent]::new($Requirement, "Validate", "Start")
$result = &$Requirement.Test
[RequirementEvent]::new($Requirement, "Validate", "Stop", $result)
if (-not $result) {
Write-Error "Failed to apply Requirement '$($Requirement.Name)'"
}
}
}
}
# applies an array of requirements
function applyRequirements([Requirement[]]$Requirements) {
$Requirements | % { applyRequirement $_ }
$Requirements | % { applyRequirement $_ }
}
# run the Test method of a requirement
function testRequirement([Requirement]$Requirement) {
if ($Requirement.Test) {
[RequirementEvent]::new($Requirement, "Test", "Start")
$result = &$Requirement.Test
[RequirementEvent]::new($Requirement, "Test", "Stop", $result)
}
}
# tests an array of requirements
function testRequirements([Requirement[]]$Requirements) {
$Requirements | % { testRequirement $_ }
}
# sorts an array of Requirements in topological order
function sortRequirements([Requirement[]]$Requirements) {
$stages = @()
while ($Requirements) {
$nextStages = $Requirements `
| ? { -not ($_.DependsOn | ? { $_ -notin $stages.Name }) }
if (-not $nextStages) {
throw "Could not resolve the dependencies for Requirements with names: $($Requirements.Name -join ', ')"
}
$Requirements = $Requirements | ? { $_.Name -notin $nextStages.Name }
$stages += $nextStages
$stages = @()
while ($Requirements) {
$nextStages = $Requirements `
| ? { -not ($_.DependsOn | ? { $_ -notin $stages.Name }) }
if (-not $nextStages) {
throw "Could not resolve the dependencies for Requirements with names: $($Requirements.Name -join ', ')"
}
$stages
$Requirements = $Requirements | ? { $_.Name -notin $nextStages.Name }
$stages += $nextStages
}
$stages
}

View File

@ -12,68 +12,68 @@ $ErrorActionPreference = "Stop"
Dsc parameter set is unsupported due to cross-platform limitations
#>
function New-Requirement {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
[OutputType([Requirement])]
[CmdletBinding()]
Param(
# The unique identifier for the Requirement
[Parameter(ParameterSetName = "Script")]
[Parameter(ParameterSetName = "Dsc")]
[string] $Name,
# A description of the Requirement
[Parameter(Mandatory, ParameterSetName = "Script")]
[Parameter(Mandatory, ParameterSetName = "Dsc")]
[string] $Describe,
# The Test condition that determines if the Requirement is in its desired state
[Parameter(ParameterSetName = "Script")]
[scriptblock] $Test,
# The Set condition that Sets the Requirement to its desired state
[Parameter(ParameterSetName = "Script")]
[scriptblock] $Set,
# The list of Requirement Names that must be in desired state prior to this Requirement
[Parameter(ParameterSetName = "Script")]
[Parameter(ParameterSetName = "Dsc")]
[ValidateNotNull()]
[string[]] $DependsOn = @(),
# The name of the DSC resource associated with the Requirement
[Parameter(Mandatory, ParameterSetName = "Dsc")]
[ValidateNotNullOrEmpty()]
[string]$ResourceName,
# The module containing the DSC resource
[Parameter(Mandatory, ParameterSetName = "Dsc")]
[ValidateNotNullOrEmpty()]
[string]$ModuleName,
# The properties passed through to the DSC resource
[Parameter(Mandatory, ParameterSetName = "Dsc")]
[ValidateNotNullOrEmpty()]
[hashtable]$Property
)
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
[OutputType([Requirement])]
[CmdletBinding()]
Param(
# The unique identifier for the Requirement
[Parameter(ParameterSetName = "Script")]
[Parameter(ParameterSetName = "Dsc")]
[string] $Name,
# A description of the Requirement
[Parameter(Mandatory, ParameterSetName = "Script")]
[Parameter(Mandatory, ParameterSetName = "Dsc")]
[string] $Describe,
# The Test condition that determines if the Requirement is in its desired state
[Parameter(ParameterSetName = "Script")]
[scriptblock] $Test,
# The Set condition that Sets the Requirement to its desired state
[Parameter(ParameterSetName = "Script")]
[scriptblock] $Set,
# The list of Requirement Names that must be in desired state prior to this Requirement
[Parameter(ParameterSetName = "Script")]
[Parameter(ParameterSetName = "Dsc")]
[ValidateNotNull()]
[string[]] $DependsOn = @(),
# The name of the DSC resource associated with the Requirement
[Parameter(Mandatory, ParameterSetName = "Dsc")]
[ValidateNotNullOrEmpty()]
[string]$ResourceName,
# The module containing the DSC resource
[Parameter(Mandatory, ParameterSetName = "Dsc")]
[ValidateNotNullOrEmpty()]
[string]$ModuleName,
# The properties passed through to the DSC resource
[Parameter(Mandatory, ParameterSetName = "Dsc")]
[ValidateNotNullOrEmpty()]
[hashtable]$Property
)
switch ($PSCmdlet.ParameterSetName) {
"Script" {
[Requirement]@{
Name = $Name
Describe = $Describe
Test = $Test
Set = $Set
DependsOn = $DependsOn
}
}
"Dsc" {
$dscParams = @{
Name = $ResourceName
ModuleName = $ModuleName
Property = $Property
}
[Requirement]@{
Name = $Name
Describe = $Describe
Test = { Invoke-DscResource -Method "Test" @dscParams }.GetNewClosure()
Set = { Invoke-DscResource -Method "Set" @dscParams }.GetNewClosure()
DependsOn = $DependsOn
}
}
switch ($PSCmdlet.ParameterSetName) {
"Script" {
[Requirement]@{
Name = $Name
Describe = $Describe
Test = $Test
Set = $Set
DependsOn = $DependsOn
}
}
"Dsc" {
$dscParams = @{
Name = $ResourceName
ModuleName = $ModuleName
Property = $Property
}
[Requirement]@{
Name = $Name
Describe = $Describe
Test = { Invoke-DscResource -Method "Test" @dscParams }.GetNewClosure()
Set = { Invoke-DscResource -Method "Set" @dscParams }.GetNewClosure()
DependsOn = $DependsOn
}
}
}
}
<#
@ -83,15 +83,15 @@ function New-Requirement {
The RequirementEvents logged from each stage of the Requirement lifecycle
#>
function Invoke-Requirement {
[CmdletBinding()]
[OutputType([RequirementEvent])]
Param(
# The Requirements to put in their desired state
[Parameter(Mandatory, ValueFromPipeline)]
[Requirement[]] $Requirement
)
[CmdletBinding()]
[OutputType([RequirementEvent])]
Param(
# The Requirements to put in their desired state
[Parameter(Mandatory, ValueFromPipeline)]
[Requirement[]] $Requirement
)
applyRequirements (sortRequirements $input)
applyRequirements (sortRequirements $input)
}
<#
@ -99,15 +99,15 @@ function Invoke-Requirement {
Tests whether a requirement is in its desired state
#>
function Test-Requirement {
[CmdletBinding()]
Param(
# The Requirement to test its desired state
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[Requirement] $Requirement
)
[CmdletBinding()]
Param(
# The Requirement to test its desired state
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[Requirement[]] $Requirement
)
&$Requirement.Test
testRequirements (sortRequirements $input)
}
<#
@ -115,15 +115,15 @@ function Test-Requirement {
Sets the requirement to its desired state
#>
function Set-Requirement {
[CmdletBinding(SupportsShouldProcess)]
Param(
# The Requirement that sets if its in its desired state
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[Requirement] $Requirement
)
[CmdletBinding(SupportsShouldProcess)]
Param(
# The Requirement that sets if its in its desired state
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[Requirement] $Requirement
)
if ($PSCmdlet.ShouldProcess($Requirement, "Set")) {
&$Requirement.Set
}
if ($PSCmdlet.ShouldProcess($Requirement, "Set")) {
&$Requirement.Set
}
}

View File

@ -1,2 +1,2 @@
12:04:37 [MyName] BEGIN SET MyDescribe
12:04:37 [MyName] END SET
10:27:29 [MyName] BEGIN SET MyDescribe
10:27:29 [MyName] END SET

View File

@ -1,2 +1,2 @@
12:04:37 [MyName] BEGIN TEST MyDescribe
12:04:37 [MyName] END TEST => True
10:27:29 [MyName] BEGIN TEST MyDescribe
10:27:29 [MyName] END TEST => True

View File

@ -1,6 +1,6 @@
12:04:37 [MyName] BEGIN TEST MyDescribe
12:04:37 [MyName] END TEST => False
12:04:37 [MyName] BEGIN SET MyDescribe
12:04:37 [MyName] END SET
12:04:37 [MyName] BEGIN TEST MyDescribe
12:04:37 [MyName] END TEST => True
10:27:29 [MyName] BEGIN TEST MyDescribe
10:27:29 [MyName] END TEST => False
10:27:29 [MyName] BEGIN SET MyDescribe
10:27:29 [MyName] END SET
10:27:29 [MyName] BEGIN TEST MyDescribe
10:27:29 [MyName] END TEST => True

View File

@ -1,3 +1,3 @@
12:04:37 [ ] MyDescribe
10:27:29 [ ] MyDescribe
12:04:37 [ √ ] MyDescribe
10:27:29 [ √ ] MyDescribe

View File

@ -1,3 +1,3 @@
12:04:37 [ ] MyDescribe
10:27:29 [ ] MyDescribe
12:04:37 [ √ ] MyDescribe
10:27:29 [ √ ] MyDescribe

View File

@ -1,3 +1,3 @@
12:04:37 [ ] MyDescribe
10:27:29 [ ] MyDescribe
12:04:37 [ √ ] MyDescribe
10:27:29 [ √ ] MyDescribe

View File

@ -1,6 +1,6 @@
Date Method State Result Requirement
---- ------ ----- ------ -----------
9/21/19 12:04:37 AM Set Start MyName
9/21/19 12:04:37 AM Set Stop True MyName
9/21/19 10:27:29 AM Set Start MyName
9/21/19 10:27:29 AM Set Stop True MyName

View File

@ -1,6 +1,6 @@
Date Method State Result Requirement
---- ------ ----- ------ -----------
9/21/19 12:04:37 AM Test Start MyName
9/21/19 12:04:37 AM Test Stop True MyName
9/21/19 10:27:29 AM Test Start MyName
9/21/19 10:27:29 AM Test Stop True MyName

View File

@ -1,10 +1,10 @@
Date Method State Result Requirement
---- ------ ----- ------ -----------
9/21/19 12:04:37 AM Test Start MyName
9/21/19 12:04:37 AM Test Stop False MyName
9/21/19 12:04:37 AM Set Start MyName
9/21/19 12:04:37 AM Set Stop True MyName
9/21/19 12:04:37 AM Validate Start MyName
9/21/19 12:04:37 AM Validate Stop True MyName
9/21/19 10:27:29 AM Test Start MyName
9/21/19 10:27:29 AM Test Stop False MyName
9/21/19 10:27:29 AM Set Start MyName
9/21/19 10:27:29 AM Set Stop True MyName
9/21/19 10:27:29 AM Validate Start MyName
9/21/19 10:27:29 AM Validate Stop True MyName

View File

@ -7,103 +7,99 @@ $SourceRoot = "$RepoRoot/src"
$PlatformLacksDscSupport = $PSVersionTable.PSEdition -eq "Core"
if (-not $PlatformLacksDscSupport) {
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$isAdmin = $identity.groups -match "S-1-5-32-544"
if (-not $isAdmin) {
throw @"
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$isAdmin = $identity.groups -match "S-1-5-32-544"
if (-not $isAdmin) {
throw @"
You are running PowerShell 5 and are therefore testing DSC resources.
You must be running as admin to test DSC resources.
"@
}
}
}
Describe "New-Requirement" {
Context "'Script' parameter set" {
$requirement = @{
Describe = "My Requirement"
Test = { 1 }
Set = { 2 }
}
It "Should not throw" {
{ New-Requirement @requirement } | Should -Not -Throw
}
It "Should not be empty" {
New-Requirement @requirement | Should -BeTrue
}
Context "'Script' parameter set" {
$requirement = @{
Describe = "My Requirement"
Test = { 1 }
Set = { 2 }
}
Context "'Dsc' parameter set" {
It "Should not be empty" -Skip:$PlatformLacksDscSupport {
$requirement = @{
Describe = "My Dsc Requirement"
ResourceName = "File"
ModuleName = "PSDesiredStateConfiguration"
Property = @{
Contents = ""
DestinationFile = ""
}
}
New-Requirement @requirement | Should -BeTrue
}
It "Should not throw" {
{ New-Requirement @requirement } | Should -Not -Throw
}
It "Should not be empty" {
New-Requirement @requirement | Should -BeTrue
}
}
Context "'Dsc' parameter set" {
It "Should not be empty" -Skip:$PlatformLacksDscSupport {
$requirement = @{
Describe = "My Dsc Requirement"
ResourceName = "File"
ModuleName = "PSDesiredStateConfiguration"
Property = @{
Contents = ""
DestinationFile = ""
}
}
New-Requirement @requirement | Should -BeTrue
}
}
}
Describe "Invoke-Requirement" {
Context "Normal Requirement" {
It "Should not error" {
$requirement = @{
Test = { 1 }
}
{ Invoke-Requirement $requirement } | Should -Not -Throw
}
Context "Normal Requirement" {
It "Should not error" {
$requirement = @{
Test = { 1 }
}
{ Invoke-Requirement $requirement } | Should -Not -Throw
}
Context "DSC Requirement" {
It "Should apply the DSC resource" -Skip:$PlatformLacksDscSupport {
$tempFilePath = "$env:TEMP\_dsctest_$(New-Guid).txt"
$content = "Hello world"
$params = @{
Name = "[file]MyFile"
Describe = "My Dsc Requirement"
ResourceName = "File"
ModuleName = "PSDesiredStateConfiguration"
Property = @{
Contents = $content
DestinationPath = $tempFilePath
Force = $true
}
}
New-Requirement @params | Invoke-Requirement
Get-Content $tempFilePath | Should -Be $content
Remove-Item $tempFilePath
}
Context "DSC Requirement" {
It "Should apply the DSC resource" -Skip:$PlatformLacksDscSupport {
$tempFilePath = "$env:TEMP\_dsctest_$(New-Guid).txt"
$content = "Hello world"
$params = @{
Name = "[file]MyFile"
Describe = "My Dsc Requirement"
ResourceName = "File"
ModuleName = "PSDesiredStateConfiguration"
Property = @{
Contents = $content
DestinationPath = $tempFilePath
Force = $true
}
}
New-Requirement @params | Invoke-Requirement
Get-Content $tempFilePath | Should -Be $content
Remove-Item $tempFilePath
}
}
}
Describe "Test-Requirement" {
It "Should not error" {
$requirement = @{
Test = { $true }
}
{ Test-Requirement $requirement } | Should -Not -Throw
}
It "Should pass through falsey values" {
$requirement = @{
Test = { $false }
}
Test-Requirement $requirement | Should -BeFalse
}
It "Should pass through truthy values" {
$requirement = @{
Test = { $true }
}
Test-Requirement $requirement | Should -BeTrue
It "Should not error" {
$requirement = @{
Test = { $true }
}
{ Test-Requirement $requirement } | Should -Not -Throw
}
It "Should only emit 'Test' events" {
$requirements = @(
@{ Test = { $true } },
@{ Set = { $true } }
)
$events = $requirements | Test-Requirement
$events | % { $_.Method | Should -Be "Test" }
}
}
Describe "Set-Requirement" {
It "Should not error" {
$requirement = @{
Set = { $false }
}
{ Invoke-Requirement $requirement } | Should -Not -Throw
It "Should not error" {
$requirement = @{
Set = { $false }
}
{ Invoke-Requirement $requirement } | Should -Not -Throw
}
}