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 } } } }