Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 258 additions & 0 deletions test/powershell/engine/Module/ModuleSpecification.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

using namespace Microsoft.PowerShell.Commands

# Take a hashtable and return two copies,
# one with the key/value pair, the other without
function PermuteHashtableOnProperty
{
param([hashtable[]]$Hashtable, [string]$Key, [object]$Value)

foreach ($ht in $Hashtable)
{
$ht.Clone()
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as an aside doing @{} + $ht is essentially equivalent to .Clone()

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha! Which do you consider nicer (for our purposes)?


foreach ($ht in $Hashtable)
{
$ht2 = $ht.Clone()
$ht2.$Key = $Value
$ht2
}
}

# Take a base hashtable and produce all possible
# combinations of that hashtable with the keys
# and values in the key/value hashtable
function PermuteHashtable
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the test structures be initialized statically? It seems to be more simple and visual than to understand this helper code. What do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been thinking about this. You're definitely right that the number of possible ModuleSpecification field combinations is lower than I imagined initially.

On the other hand, for every field that could be added to a ModuleSpecification, we would have to generate exponentially more tests vs add a single field in here. Perhaps that's unlikely. But I can see something like PSEdition being added, which would take the number of static cases from 6 to 12.

{
param([hashtable]$InitialTable, [hashtable]$KeyValues)

$l = $InitialTable
foreach ($key in $KeyValues.Keys)
{
$l = PermuteHashtableOnProperty -Hashtable $l -Key $key -Value $KeyValues[$key]
}

$l
}

$guid = [guid]::NewGuid()

$modSpecRequired = @{
ModuleName = "ModSpecRequired"
RequiredVersion = "3.0.2"
}

$requiredOptionalConstraints = @{ Guid = $guid }

$modSpecRange = @{
ModuleName = "ModSpecRequired"
ModuleVersion = "3.0.1"
}

$rangeOptionalConstraints = @{ MaximumVersion = "3.2.0"; Guid = $guid }

Describe "ModuleSpecification objects and logic" -Tag "CI" {

BeforeAll {
$testCases = [System.Collections.Generic.List[hashtable]]::new()

foreach ($case in (PermuteHashtable -InitialTable $modSpecRequired -KeyValues $requiredOptionalConstraints))
{
$testCases.Add(@{
ModuleSpecification = $case
Keys = ($case.Keys -join ",")
})
}

foreach ($case in (PermuteHashtable -InitialTable $modSpecRange -KeyValues $rangeOptionalConstraints))
{
$testCases.Add(@{
ModuleSpecification = $case
Keys = ($case.Keys -join ",")
})
}

$testCases = $testCases.ToArray()
}

Context "ModuleSpecification construction and string parsing" {

BeforeAll {
$differentFieldCases = @(
@{
TestName = "Guid"
ModSpec1 = @{ Guid = [guid]::NewGuid(); ModuleName = "TestModule"; ModuleVersion = "1.0" }
ModSpec2 = @{ Guid = [guid]::NewGuid(); ModuleName = "TestModule"; ModuleVersion = "1.0" }
},
@{
TestName = "RequiredVersion"
ModSpec1 = @{ ModuleName = "Module"; RequiredVersion = "3.0" }
ModSpec2 = @{ ModuleName = "Module"; RequiredVersion = "3.1" }
},
@{
TestName = "Version/MaxVersion-present"
ModSpec1 = @{ ModuleName = "ThirdModule"; ModuleVersion = "2.1" }
ModSpec2 = @{ ModuleName = "ThirdModule"; MaximumVersion = "2.1" }
},
@{
TestName = "RequiredVersion/Version-present"
ModSpec1 = @{ ModuleName = "FourthModule"; RequiredVersion = "3.0" }
ModSpec2 = @{ ModuleName = "FourthModule"; ModuleVersion = "3.0" }
}
)
}

It "Can be created from a name" {
$ms = [ModuleSpecification]::new("NamedModule")
$ms | Should -Not -BeNull
$ms.Name | Should -BeExactly "NamedModule"
}

It "Can be created from Hashtable with keys: <Keys>" -TestCases $testCases {
param([hashtable]$ModuleSpecification, [string]$Keys)
$ms = [ModuleSpecification]::new($ModuleSpecification)

$ms.Name | Should -BeExactly $ModuleSpecification.ModuleName

if ($ModuleSpecification.Guid)
{
$ms.Guid | Should -Be $ModuleSpecification.Guid
}

if ($ModuleSpecification.ModuleVersion)
{
$ms.Version | Should -Be $ModuleSpecification.ModuleVersion
}

if ($ModuleSpecification.RequiredVersion)
{
$ms.RequiredVersion | Should -Be $ModuleSpecification.RequiredVersion
}

if ($ModuleSpecification.MaximumVersion)
{
$ms.MaximumVersion | Should -Be $ModuleSpecification.MaximumVersion
}
}

It "Can be reconstructed from self.ToString() with keys: <Keys>" -TestCases $testCases {
param([hashtable]$ModuleSpecification, [string]$Keys)

$ms = [ModuleSpecification]::new($ModuleSpecification)

[ModuleSpecification]$clone = $null
[ModuleSpecification]::TryParse(($ms.ToString()), [ref]$clone) | Should -BeTrue

$clone.Name | Should -Be $ModuleSpecification.ModuleName

if ($ModuleSpecification.RequiredVersion)
{
$clone.RequiredVersion | Should -Be $ModuleSpecification.RequiredVersion
}

if ($ModuleSpecification.Version)
{
$clone.Version | Should -Be $ModuleSpecification.Version
}

if ($ModuleSpecification.MaximumVersion)
{
$clone.MaximumVersion | Should -Be $ModuleSpecification.MaximumVersion
}

if ($ModuleSpecification.Guid)
{
$clone.Guid | Should -Be $ModuleSpecification.Guid
}
}
}

Context "ModuleSpecification comparison" {

BeforeAll {
$modSpecAsm = [ModuleSpecification].Assembly
$modSpecComparerType = $modSpecAsm.GetType("Microsoft.PowerShell.Commands.ModuleSpecificationComparer")
$comparer = [System.Activator]::CreateInstance($modSpecComparerType)
}

It "Module specifications with same fields <Keys> are equal" -TestCases $testCases {
param([hashtable]$ModuleSpecification, [string]$Keys)

$ms = [ModuleSpecification]::new($ModuleSpecification)
$ms2 = [ModuleSpecification]::new($ModuleSpecification)

$comparer.Equals($ms, $ms2) | Should -BeTrue
}

It "Module specifications with same fields <Keys> have the same hash code" -TestCases $testCases {
param([hashtable]$ModuleSpecification, [string]$Keys)

$ms = [ModuleSpecification]::new($ModuleSpecification)
$ms2 = [ModuleSpecification]::new($ModuleSpecification)

$comparer.GetHashCode($ms) | Should -Be $comparer.GetHashCode($ms2)
}

It "Module specifications with different <TestName> fields are not equal" -TestCases $differentFieldCases {
param($TestName, $ModSpec1, $ModSpec2)
$ms1 = [ModuleSpecification]::new($ModSpec1)
$ms2 = [ModuleSpecification]::new($ModSpec2)

$comparer.Equals($ms1, $ms2) | Should -BeFalse
}

It "Compares two null module specifications as equal" {
$comparer.Equals($null, $null) | Should -BeTrue
}

It "Compares a null module specification with another as unequal" {
$ms = [ModuleSpecification]::new(@{
MOduleName = "NonNullModule"
Guid = [guid]::NewGuid()
RequiredVersion = "3.2.1"
})

$comparer.Equals($ms, $null) | Should -BeFalse
}

It "Succeeds to get a hash code from a null module specification" {
$comparer.GetHashCode($null) | Should -Not -BeNull
}
}

Context "Invalid ModuleSpecification initialization" {
BeforeAll {
$testCases = @(
@{
TestName = "Version+RequiredVersion"
ModuleSpecification = @{ Name = "BadVersionModule"; ModuleVersion = "3.1"; RequiredVersion = "3.1" }
},
@{
TestName = "NoName"
ModuleSpecification = @{ ModuleVersion = "0.2" }
},
@{
TestName = "BadField"
ModuleSpecification = @{ Name = "StrangeFieldModule"; RequiredVersion = "7.4"; Duck = "1.2" }
},
@{
TestName = "BadType"
ModuleSpecification = @{ Name = "BadTypeModule"; RequiredVersion = "Hello!" }
}
)
}

It "Cannot create from a null argument" {
{ [ModuleSpecification]::new($null) } | Should -Throw
}

It "Cannot create from invalid module hashtables: <TestName>" -TestCases $testCases {
param([string]$TestName, [hashtable]$ModuleSpecification)

{ [ModuleSpecification]::new($ModuleSpecification) } | Should -Throw
}
}
}