In Part 1 I reorganised my NuGet source synchronization tool’s code to make it testable, with the specific aim of allowing pre-release packages to be synchronized. In particular, the affected code was isolated into this function:
function Parse-PackageItem {
param(
[string]$Package
)
$idVer = $Package.Split(' ')
if ($idVer.Length -eq 2) {
$id = $idVer[0]
[string]$ver = $idVer[1]
$parts = $ver.Split('.')
if ($parts.Length -eq 3) {
[int]$major = $parts[0]
[int]$minor = $parts[1]
[int]$patch = $parts[2]
new-object -TypeName PSCustomObject -Property @{
package = $Package
id = $id
version = $ver
major = $major
minor = $minor
patch = $patch
}
}
}
}
This is a challenge because the pre-release part of the version starts with a hyphen, followed by a series of dot separated identifiers after that. Each identifier consists of letters, digits and hyphens.
A pre-release version MAY be denoted by appending a hyphen and a series of dot separated identifiers immediately following the patch version. Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes. Pre-release versions have a lower precedence than the associated normal version. A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version. Examples: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92.
https://semver.org/#spec-item-9
For our purposes, the pre-release label is whatever follows the hyphen.
Creating the first tests
This is the Pester test that we created previously:
if (Get-Module SyncNuGetRepos -All) {
Remove-Module SyncNuGetRepos
}
Import-Module "$PSScriptRoot\..\bin\Debug\SyncNuGetRepos\SyncNuGetRepos.psm1" -WarningAction SilentlyContinue
Describe "Parse-PackageItem" {
Context "Exists" {
It "Runs" {
Parse-PackageItem
}
}
}
First I add a test for the non-pre-release case. This would exist if TDD was done from the beginning.
Context "Non pre-release Item parts" {
$parts = Parse-PackageItem -Package 'package 1.0.123'
It "Given 'package 1.0.123', <property> should be <value>" -TestCases @(
@{ property = 'id'; value = 'package' }
@{ property = 'major'; value = '1' }
@{ property = 'minor'; value = '0' }
@{ property = 'patch'; value = '123' }
@{ property = 'prerelease'; value = '' }
) {
param ($property, $value)
"$($parts.$property)" | should -Be $value
}
}
Context Non pre-release Item parts
[+] Given 'package 1.0.123', 'id' should be 'package' 69ms
[+] Given 'package 1.0.123', 'major' should be '1' 23ms
[+] Given 'package 1.0.123', 'minor' should be '0' 14ms
[+] Given 'package 1.0.123', 'patch' should be '123' 15ms
[+] Given 'package 1.0.123', 'prerelease' should be <empty> 14ms
The tests pass.
The good thing is that we can easily modify this test for the other scenarios we want to test. For the next test I am adding a simple pre-release label:
Context "Simple pre-release Item parts" {
$parts = Parse-PackageItem -Package 'package 1.0.123-PreRelease'
It "Given 'package 1.0.123', <property> should be <value>" -TestCases @(
@{ property = 'id'; value = 'package' }
@{ property = 'major'; value = '1' }
@{ property = 'minor'; value = '0' }
@{ property = 'patch'; value = '123' }
@{ property = 'prerelease'; value = 'PreRelease' }
) {
param ($property, $value)
"$($parts.$property)" | should -Be $value
}
}
As expected, this failed:
Context Simple pre-release Item parts
[-] Error occurred in Context block 173ms
FormatException: Input string was not in a correct format.
PSInvalidCastException: Cannot convert value "123-PreRelease" to type "System.Int32". Error: "Input string was not in a correct format."
RuntimeException: Cannot convert value "123-PreRelease" to type "System.Int32". Error: "Input string was not in a correct format."
at Parse-PackageItem, C:\VSTS\ContinuousIntegration\SyncNuGetRepos\SyncNuGetRepos\bin\Debug\SyncNuGetRepos\SyncNuGetRepos.psm1: line 109
at <ScriptBlock>, C:\VSTS\ContinuousIntegration\SyncNuGetRepos\SyncNuGetRepos\Tests\Parse-PackageItem.tests.ps1: line 25
at DescribeImpl, C:\Program Files\WindowsPowerShell\Modules\Pester\4.3.1\Functions\Describe.ps1: line 161
The immediate error was the integer conversion, suggesting that the patch should be fixed. Instead, the pre-release label modifies the entire version, not just the patch. I change
[string]$ver = $idVer[1]
$parts = $ver.Split('.')
becomes
[string]$ver = $idVer[1]
$verPreRel = $ver.Split('-')
[string]$verParts = $verPreRel[0]
[string]$preRel = ''
if ($verParts.Length -eq 2) {
$preRel = $verPreRel[1]
}
$parts = $verParts.Split('.')
and I assign $preRel to the prerelease property:
function Parse-PackageItem {
param(
[string]$Package
)
$idVer = $Package.Split(' ')
if ($idVer.Length -eq 2) {
$id = $idVer[0]
[string]$ver = $idVer[1]
$verPreRel = $ver.Split('-')
[string]$verParts = $verPreRel[0]
[string]$preRel = ''
if ($verPreRel.Length -eq 2) {
$preRel = $verPreRel[1]
}
$parts = $verParts.Split('.')
if ($parts.Length -eq 3) {
[int]$major = $parts[0]
[int]$minor = $parts[1]
[int]$patch = $parts[2]
new-object -TypeName PSCustomObject -Property @{
package = $Package
id = $id
version = $ver
major = $major
minor = $minor
patch = $patch
prerelease = $preRel
}
}
}
}
I’m cheating a little. The test failed, and I had to fix a typo.
The next test verifies that the pre-release part can consist of dot-separtated identifiers:
Context "Dot-separated identifier pre-release Item parts" {
$parts = Parse-PackageItem -Package 'package 1.0.123-Pre.Release'
It "Given 'package 1.0.123', <property> should be <value>" -TestCases @(
@{ property = 'id'; value = 'package' }
@{ property = 'major'; value = '1' }
@{ property = 'minor'; value = '0' }
@{ property = 'patch'; value = '123' }
@{ property = 'prerelease'; value = 'Pre.Release' }
) {
param ($property, $value)
"$($parts.$property)" | should -Be $value
}
}
The test passes. The next test is for the pre-release part containing a hyphen:
Context "Hyphened identifier pre-release Item parts" {
$parts = Parse-PackageItem -Package 'package 1.0.123-Pre-Release'
It "Given 'package 1.0.123', <property> should be <value>" -TestCases @(
@{ property = 'id'; value = 'package' }
@{ property = 'major'; value = '1' }
@{ property = 'minor'; value = '0' }
@{ property = 'patch'; value = '123' }
@{ property = 'prerelease'; value = 'Pre-Release' }
) {
param ($property, $value)
"$($parts.$property)" | should -Be $value
}
}
As expected, this failed:
[-] Given 'package 1.0.123', 'prerelease' should be 'Pre-Release' 13ms
Expected strings to be the same, but they were different.
Expected length: 11
Actual length: 0
Strings differ at index 0.
Expected: 'Pre-Release'
But was: ''
-----------^
60: "$($parts.$property)" | should -Be $value
at <ScriptBlock>, C:\VSTS\ContinuousIntegration\SyncNuGetRepos\SyncNuGetRepos\Tests\Parse-PackageItem.tests.ps1: line 60
The value was empty because of $ver.Split(‘-‘) returning more than two parts. I check the String.Split documentation and see that I can make this $ver.Split(‘-‘, 2). After I make the change, the test passes.
Now my function looks like this:
function Parse-PackageItem {
param(
[string]$Package
)
$idVer = $Package.Split(' ')
if ($idVer.Length -eq 2) {
$id = $idVer[0]
[string]$ver = $idVer[1]
$verPreRel = $ver.Split('-', 2)
[string]$verParts = $verPreRel[0]
[string]$preRel = ''
if ($verPreRel.Length -eq 2) {
$preRel = $verPreRel[1]
}
$parts = $verParts.Split('.')
if ($parts.Length -eq 3) {
[int]$major = $parts[0]
[int]$minor = $parts[1]
[int]$patch = $parts[2]
new-object -TypeName PSCustomObject -Property @{
package = $Package
id = $id
version = $ver
major = $major
minor = $minor
patch = $patch
prerelease = $preRel
}
}
}
}