diff --git a/AU/Private/AUVersion.ps1 b/AU/Private/AUVersion.ps1 index af399cb..842fe00 100644 --- a/AU/Private/AUVersion.ps1 +++ b/AU/Private/AUVersion.ps1 @@ -1,56 +1,76 @@ +enum SemVer { + V1 + V2 + EnhancedV2 +} + class AUVersion : System.IComparable { [version] $Version [string] $Prerelease [string] $BuildMetadata - AUVersion([version] $version, [string] $prerelease, [string] $buildMetadata) { + hidden AUVersion([version] $version, [string] $prerelease, [string] $buildMetadata) { if (!$version) { throw 'Version cannot be null.' } - $this.Version = [AUVersion]::NormalizeVersion($version) - $this.Prerelease = [AUVersion]::NormalizePrerelease($prerelease) -join '.' + $this.Version = [AUVersion]::NormalizeVersion($version) + $this.Prerelease = [AUVersion]::NormalizePrerelease($prerelease) -join '.' $this.BuildMetadata = $buildMetadata } - AUVersion($input) { - if (!$input) { throw 'Input cannot be null.' } - $v = [AUVersion]::Parse($input -as [string]) - $this.Version = $v.Version - $this.Prerelease = $v.Prerelease + AUVersion($value) { + if (!$value) { throw 'Input cannot be null.' } + $v = [AUVersion]::Parse($value -as [string]) + $this.Version = $v.Version + $this.Prerelease = $v.Prerelease $this.BuildMetadata = $v.BuildMetadata } - static [AUVersion] Parse([string] $input) { return [AUVersion]::Parse($input, $true) } + static [AUVersion] Parse([string] $value) { + return [AUVersion]::Parse($value, $true) + } + + static [AUVersion] Parse([string] $value, [bool] $strict) { + return [AUVersion]::Parse($value, $strict, [SemVer]::V2) + } + + static [AUVersion] Parse([string] $value, [bool] $strict, [SemVer] $semver) { + if (!$value) { throw 'Version cannot be null.' } + $v = [ref] $null + if (![AUVersion]::TryParse($value, $v, $strict, $semver)) { + throw "Invalid SemVer $semver version: `"$value`"." + } + return $v.Value + } - static [AUVersion] Parse([string] $input, [bool] $strict) { - if (!$input) { throw 'Version cannot be null.' } - $reference = [ref] $null - if (![AUVersion]::TryParse($input, $reference, $strict)) { throw "Invalid version: $input." } - return $reference.Value + static [bool] TryParse([string] $value, [ref] $result) { + return [AUVersion]::TryParse($value, $result, $true) } - static [bool] TryParse([string] $input, [ref] $result) { return [AUVersion]::TryParse($input, $result, $true) } + static [bool] TryParse([string] $value, [ref] $result, [bool] $strict) { + return [AUVersion]::TryParse($value, $result, $strict, [SemVer]::V2) + } - static [bool] TryParse([string] $input, [ref] $result, [bool] $strict) { + static [bool] TryParse([string] $value, [ref] $result, [bool] $strict, [SemVer] $semver) { $result.Value = [AUVersion] $null - if (!$input) { return $false } + if (!$value) { return $false } $pattern = [AUVersion]::GetPattern($strict) - if ($input -notmatch $pattern) { return $false } - $reference = [ref] $null - if (![version]::TryParse($Matches.version, $reference)) { return $false } - $pr = $Matches.prerelease - $bm = $Matches.buildMetadata - if ($pr -and !$strict) { $pr = [AUVersion]::RefineParts($pr) } - if ($bm -and !$strict) { $bm = [AUVersion]::RefineParts($bm) } - $result.Value = [AUVersion]::new($reference.Value, $pr, $bm) + if ($value -notmatch $pattern) { return $false } + $v = [ref] $null + if (![version]::TryParse($Matches.version, $v)) { return $false } + $pr = [ref] $null + if (![AUVersion]::TryRefineIdentifiers($Matches.prerelease, $pr, $strict, $semver)) { return $false } + $bm = [ref] $null + if (![AUVersion]::TryRefineIdentifiers($Matches.buildMetadata, $bm, $strict, $semver)) { return $false } + $result.Value = [AUVersion]::new($v.Value, $pr.Value, $bm.Value) return $true } hidden static [version] NormalizeVersion([version] $value) { - if ($value.Revision -eq 0) { - return $value.ToString(3) - } if ($value.Build -eq -1) { return [version] "$value.0" } + if ($value.Revision -eq 0) { + return [version] $value.ToString(3) + } return $value } @@ -59,7 +79,7 @@ class AUVersion : System.IComparable { if ($value) { $value -split '\.' | ForEach-Object { # if identifier is exclusively numeric, cast it to an int - if ($_ -match '^[0-9]+$') { + if ($_ -match '^\d+$') { $result += [int] $_ } else { $result += $_ @@ -75,27 +95,39 @@ class AUVersion : System.IComparable { $identifierPattern = "[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*" return "^$versionPattern(?:-(?$identifierPattern))?(?:\+(?$identifierPattern))?`$" } else { - $identifierPattern = "[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+| \d+)*" - return "$versionPattern(?:[- ]*(?$identifierPattern))?(?:[+ *](?$identifierPattern))?" + $identifierPattern = "[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+| +\d+)*" + return "$versionPattern(?:(?:-| *)(?$identifierPattern))?(?:(?:\+| *)(?$identifierPattern))?" } } - hidden static [string] RefineParts([string] $value) { - $result = if ($value -match '^(?[A-Za-z]+)(?\d+)$') { - '{0}.{1}' -f $Matches.identifier, $Matches.digits - } else { - $value.Replace(' ', '.') + hidden static [bool] TryRefineIdentifiers([string] $value, [ref] $result, [bool] $strict, [SemVer] $semver) { + $result.Value = [string] '' + if (!$value) { return $true } + if (!$strict) { $value = $value -replace ' +', '.' } + if ($semver -eq [SemVer]::V1) { + # SemVer1 means no dot-separated identifiers + if ($strict -and $value -match '\.') { return $false } + $value = $value.Replace('.', '') + } elseif ($semver -eq [SemVer]::EnhancedV2) { + # Try to improve a SemVer1 version into a SemVer2 one + # e.g. 1.24.0-beta2 becomes 1.24.0-beta.2 + if ($value -match '^(?[A-Za-z-]+)(?\d+)$') { + $value = '{0}.{1}' -f $Matches.identifier, $Matches.digits + } } - return $result + $result.Value = $value + return $true } - [AUVersion] WithVersion([version] $version) { return [AUVersion]::new($version, $this.Prerelease, $this.BuildMetadata) } + [AUVersion] WithVersion([version] $version) { + return [AUVersion]::new($version, $this.Prerelease, $this.BuildMetadata) + } [int] CompareTo($obj) { - if ($obj -eq $null) { return 1 } - if ($obj -isnot [AUVersion]) { throw "AUVersion expected: $($obj.GetType())" } - $t = $this.GetAllParts() - $o = $obj.GetAllParts() + if ($null -eq $obj) { return 1 } + if ($obj -isnot [AUVersion]) { throw "[AUVersion] expected, got [$($obj.GetType())]." } + $t = $this.GetParts() + $o = $obj.GetParts() for ($i = 0; $i -lt $t.Length -and $i -lt $o.Length; $i++) { if ($t[$i].GetType() -ne $o[$i].GetType()) { $t[$i] = [string] $t[$i] @@ -113,12 +145,12 @@ class AUVersion : System.IComparable { [bool] Equals($obj) { return $this.CompareTo($obj) -eq 0 } - [int] GetHashCode() { return $this.GetAllParts().GetHashCode() } + [int] GetHashCode() { return $this.GetParts().GetHashCode() } [string] ToString() { $result = $this.Version.ToString() - if ($this.Prerelease) { $result += "-$($this.Prerelease)" } - if ($this.BuildMetadata) { $result += "+$($this.BuildMetadata)" } + if ($this.Prerelease) { $result += '-{0}' -f $this.Prerelease } + if ($this.BuildMetadata) { $result += '+{0}' -f $this.BuildMetadata } return $result } @@ -127,13 +159,9 @@ class AUVersion : System.IComparable { return $this.Version.ToString($fieldCount) } - hidden [object[]] GetAllParts() { + hidden [object[]] GetParts() { $result = , $this.Version $result += [AUVersion]::NormalizePrerelease($this.Prerelease) return $result } } - -function ConvertTo-AUVersion($Version) { - return [AUVersion] $Version -} diff --git a/AU/Public/Get-Version.ps1 b/AU/Public/Get-Version.ps1 index 4495fe9..8764926 100644 --- a/AU/Public/Get-Version.ps1 +++ b/AU/Public/Get-Version.ps1 @@ -1,11 +1,11 @@ -# Author: Thomas Démoulins +# Author: Thomas Démoulins <# .SYNOPSIS - Parses a semver-like object from a string in a flexible manner. + Parses a SemVer-like object from a string in a flexible manner. .DESCRIPTION - This function parses a string containing a semver-like version + This function parses a string containing a SemVer-like version and returns an object that represents both the version (with up to 4 parts) and optionally a pre-release and a build metadata. @@ -16,26 +16,48 @@ - extra spaces are ignored - optional delimiters can be provided to help parsing the string + Parameter -SemVer allows to specify the max supported SemVer version: + V1 (default) or V2 (requires choco v2.0.0). EnhancedV2 is about + transforming a SemVer1-like version into a SemVer2-like one when + possible (e.g. 1.61.0-beta.0 instead of 1.61.0-beta0). + + Resulting version is normalized the same way chocolatey/nuget does. + See https://learn.microsoft.com/en-us/nuget/concepts/package-versioning#normalized-version-numbers + +.EXAMPLE + Get-Version 'Current version 2.1.1 beta 2.' + + Returns 2.1.1-beta2 + +.EXAMPLE + Get-Version -SemVer V2 'Current version 2.1.1 beta 2.' + + Returns 2.1.1-beta.2 + .EXAMPLE - Get-Version 'Last version: 1.2.3 beta 3.' + Get-Version -SemVer V2 '4.0.3Beta1' - Returns 1.2.3-beta.3 + Returns 4.0.3-Beta1 .EXAMPLE - Get-Version 'https://github.com/atom/atom/releases/download/v1.24.0-beta2/AtomSetup.exe' + Get-Version -SemVer EnhancedV2 '4.0.3Beta1' - Return 1.24.0-beta.2 + Returns 4.0.3-Beta.1 .EXAMPLE - Get-Version 'http://mirrors.kodi.tv/releases/windows/win32/kodi-17.6-Krypton-x86.exe' -Delimiter '-' + Get-Version 'https://dl.airserver.com/pc32/AirServer-5.6.3-x86.msi' -Delimiter '-' - Return 17.6 + Returns 5.6.3 #> function Get-Version { [CmdletBinding()] param( + # Supported SemVer version: V1 (default) or V2 (requires choco v2.0.0). + # EnhancedV2 allows to transform a SemVer1-like version into SemVer2-like one (e.g. 1.2.0-rc.3 instead of 1.2.0-rc3) + [ValidateSet('V1', 'V2', 'EnhancedV2')] + [string] $SemVer = 'V1', # Version string to parse. - [Parameter(Mandatory=$true)] + [Parameter(Mandatory, Position=0)] [string] $Version, # Optional delimiter(s) to help locate the version in the string: the version must start and end with one of these chars. [char[]] $Delimiter @@ -46,10 +68,10 @@ function Get-Version { $regex = $Version | Select-String -Pattern "[$delimiters](\d+\.\d+[^$delimiters]*)[$delimiters]" -AllMatches foreach ($match in $regex.Matches) { $reference = [ref] $null - if ([AUVersion]::TryParse($match.Groups[1], $reference, $false)) { + if ([AUVersion]::TryParse($match.Groups[1], $reference, $false, $SemVer)) { return $reference.Value } } } - return [AUVersion]::Parse($Version, $false) + return [AUVersion]::Parse($Version, $false, $SemVer) } diff --git a/README.md b/README.md index 5744520..2cfc2c8 100644 --- a/README.md +++ b/README.md @@ -346,13 +346,13 @@ In order to help working with versions, function `Get-Version` can be called in - To force the update of the single stream, use `IncludeStream` parameter. To do so via commit message, use `[AU package\stream]` syntax. ```powershell -PS> Get-Version 'v1.3.2.7rc1' +PS> Get-Version 'v1.3.2.7rc.1' Version Prerelease BuildMetadata ------- ---------- ------------- -1.3.2.7 rc.1 +1.3.2.7 rc1 -PS> $version = Get-Version '1.3.2-beta.2+5' +PS> $version = Get-Version -SemVer V2 '1.3.2-beta.2+5' PS> $version.ToString(2) + ' => ' + $version.ToString() 1.3 => 1.3.2-beta.2+5 ``` diff --git a/tests/AUVersion.Tests.ps1 b/tests/AUVersion.Tests.ps1 new file mode 100644 index 0000000..b188743 --- /dev/null +++ b/tests/AUVersion.Tests.ps1 @@ -0,0 +1,280 @@ +remove-module AU -ea ignore +import-module $PSScriptRoot\..\AU + +Describe '[AUVersion]' -Tag version { + InModuleScope AU { + Context '[AUVersion]::Parse() for a strict SemVer1 version' { + It 'parses ""' -TestCases @( + @{Value = '01.02' ; Ver = '1.2.0' ; Pre = '' ; Build = ''} + @{Value = '01.02-pre05' ; Ver = '1.2.0' ; Pre = 'pre05'; Build = ''} + @{Value = '01.02+sha06' ; Ver = '1.2.0' ; Pre = '' ; Build = 'sha06'} + @{Value = '01.02-pre05+sha06' ; Ver = '1.2.0' ; Pre = 'pre05'; Build = 'sha06'} + @{Value = '01.02.03' ; Ver = '1.2.3' ; Pre = '' ; Build = ''} + @{Value = '01.02.03-pre05+sha06' ; Ver = '1.2.3' ; Pre = 'pre05'; Build = 'sha06'} + @{Value = '01.02.03.00' ; Ver = '1.2.3' ; Pre = '' ; Build = ''} + @{Value = '01.02.03.04' ; Ver = '1.2.3.4'; Pre = '' ; Build = ''} + @{Value = '01.02.03.04-pre05+sha06'; Ver = '1.2.3.4'; Pre = 'pre05'; Build = 'sha06'} + ) { param([string] $Value, [version] $Ver, [string] $Pre, [string] $Build) + $res = [AUVersion]::Parse($Value, $true, 'V1') + $res | Should Not BeNullOrEmpty + $res.Version | Should Be $Ver + $res.Prerelease | Should Be $Pre + $res.BuildMetadata | Should Be $Build + } + + It 'does not parse ""' -TestCases @( + @{Value = '01'} + @{Value = 'v01.02'} + @{Value = '01.02-pre.05'} + @{Value = '01.02+sha.06'} + @{Value = '01.02-pre.05+sha.06'} + @{Value = '01.02.03-pre.05+sha.06'} + @{Value = '01.02.03.04-pre.05+sha.06'} + ) { param([string] $Value) + { [AUVersion]::Parse($Value, $true, 'V1') } | Should Throw 'Invalid SemVer' + } + } + + Context '[AUVersion]::Parse() for a strict SemVer2 version' { + It 'parses ""' -TestCases @( + @{Value = '01.02' ; Ver = '1.2.0' ; Pre = '' ; Build = ''} + @{Value = '01.02-pre05' ; Ver = '1.2.0' ; Pre = 'pre05'; Build = ''} + @{Value = '01.02+sha06' ; Ver = '1.2.0' ; Pre = '' ; Build = 'sha06'} + @{Value = '01.02-pre05+sha06' ; Ver = '1.2.0' ; Pre = 'pre05'; Build = 'sha06'} + @{Value = '01.02-pre.05' ; Ver = '1.2.0' ; Pre = 'pre.5'; Build = ''} + @{Value = '01.02+sha.06' ; Ver = '1.2.0' ; Pre = '' ; Build = 'sha.06'} + @{Value = '01.02-pre.05+sha.06' ; Ver = '1.2.0' ; Pre = 'pre.5'; Build = 'sha.06'} + @{Value = '01.02.03' ; Ver = '1.2.3' ; Pre = '' ; Build = ''} + @{Value = '01.02.03-pre05+sha06' ; Ver = '1.2.3' ; Pre = 'pre05'; Build = 'sha06'} + @{Value = '01.02.03-pre.05+sha.06' ; Ver = '1.2.3' ; Pre = 'pre.5'; Build = 'sha.06'} + @{Value = '01.02.03.00' ; Ver = '1.2.3' ; Pre = '' ; Build = ''} + @{Value = '01.02.03.04' ; Ver = '1.2.3.4'; Pre = '' ; Build = ''} + @{Value = '01.02.03.04-pre05+sha06' ; Ver = '1.2.3.4'; Pre = 'pre05'; Build = 'sha06'} + @{Value = '01.02.03.04-pre.05+sha.06'; Ver = '1.2.3.4'; Pre = 'pre.5'; Build = 'sha.06'} + ) { param([string] $Value, [version] $Ver, [string] $Pre, [string] $Build) + $res = [AUVersion]::Parse($Value, $true, 'V2') + $res | Should Not BeNullOrEmpty + $res.Version | Should Be $Ver + $res.Prerelease | Should Be $Pre + $res.BuildMetadata | Should Be $Build + } + + It 'does not parse ""' -TestCases @( + @{Value = '01'} + @{Value = 'v01.02'} + ) { param([string] $Value) + { [AUVersion]::Parse($Value, $true, 'V2') } | Should Throw 'Invalid SemVer' + } + } + + Context '[AUVersion]::Parse() for a strict enhanced SemVer2 version' { + It 'parses ""' -TestCases @( + @{Value = '01.02' ; Ver = '1.2.0' ; Pre = '' ; Build = ''} + @{Value = '01.02-pre05' ; Ver = '1.2.0' ; Pre = 'pre.5'; Build = ''} + @{Value = '01.02+sha06' ; Ver = '1.2.0' ; Pre = '' ; Build = 'sha.06'} + @{Value = '01.02-pre05+sha06' ; Ver = '1.2.0' ; Pre = 'pre.5'; Build = 'sha.06'} + @{Value = '01.02-pre.05' ; Ver = '1.2.0' ; Pre = 'pre.5'; Build = ''} + @{Value = '01.02+sha.06' ; Ver = '1.2.0' ; Pre = '' ; Build = 'sha.06'} + @{Value = '01.02-pre.05+sha.06' ; Ver = '1.2.0' ; Pre = 'pre.5'; Build = 'sha.06'} + @{Value = '01.02.03' ; Ver = '1.2.3' ; Pre = '' ; Build = ''} + @{Value = '01.02.03-pre05+sha06' ; Ver = '1.2.3' ; Pre = 'pre.5'; Build = 'sha.06'} + @{Value = '01.02.03-pre.05+sha.06' ; Ver = '1.2.3' ; Pre = 'pre.5'; Build = 'sha.06'} + @{Value = '01.02.03.00' ; Ver = '1.2.3' ; Pre = '' ; Build = ''} + @{Value = '01.02.03.04' ; Ver = '1.2.3.4'; Pre = '' ; Build = ''} + @{Value = '01.02.03.04-pre05+sha06' ; Ver = '1.2.3.4'; Pre = 'pre.5'; Build = 'sha.06'} + @{Value = '01.02.03.04-pre.05+sha.06'; Ver = '1.2.3.4'; Pre = 'pre.5'; Build = 'sha.06'} + ) { param([string] $Value, [version] $Ver, [string] $Pre, [string] $Build) + $res = [AUVersion]::Parse($Value, $true, 'EnhancedV2') + $res | Should Not BeNullOrEmpty + $res.Version | Should Be $Ver + $res.Prerelease | Should Be $Pre + $res.BuildMetadata | Should Be $Build + } + + It 'does not parse ""' -TestCases @( + @{Value = '01'} + @{Value = 'v01.02'} + ) { param([string] $Value) + { [AUVersion]::Parse($Value, $true, 'EnhancedV2') } | Should Throw 'Invalid SemVer' + } + } + + Context '[AUVersion]::Parse() for a non strict SemVer1 version' { + It 'parses ""' -TestCases @( + @{Value = '01.02' ; Ver = '1.2.0' ; Pre = '' ; Build = ''} + @{Value = 'v01.02' ; Ver = '1.2.0' ; Pre = '' ; Build = ''} + @{Value = '01.02-pre05' ; Ver = '1.2.0' ; Pre = 'pre05'; Build = ''} + @{Value = '01.02+sha06' ; Ver = '1.2.0' ; Pre = '' ; Build = 'sha06'} + @{Value = '01.02-pre05+sha06' ; Ver = '1.2.0' ; Pre = 'pre05'; Build = 'sha06'} + @{Value = '01.02-pre.05' ; Ver = '1.2.0' ; Pre = 'pre05'; Build = ''} + @{Value = '01.02+sha.06' ; Ver = '1.2.0' ; Pre = '' ; Build = 'sha06'} + @{Value = '01.02-pre.05+sha.06' ; Ver = '1.2.0' ; Pre = 'pre05'; Build = 'sha06'} + @{Value = '01.02.03' ; Ver = '1.2.3' ; Pre = '' ; Build = ''} + @{Value = '01.02.03-pre05+sha06' ; Ver = '1.2.3' ; Pre = 'pre05'; Build = 'sha06'} + @{Value = '01.02.03-pre.05+sha.06' ; Ver = '1.2.3' ; Pre = 'pre05'; Build = 'sha06'} + @{Value = '01.02.03.00' ; Ver = '1.2.3' ; Pre = '' ; Build = ''} + @{Value = '01.02.03.04' ; Ver = '1.2.3.4'; Pre = '' ; Build = ''} + @{Value = '01.02.03.04-pre05+sha06' ; Ver = '1.2.3.4'; Pre = 'pre05'; Build = 'sha06'} + @{Value = '01.02.03.04-pre.05+sha.06'; Ver = '1.2.3.4'; Pre = 'pre05'; Build = 'sha06'} + ) { param([string] $Value, [version] $Ver, [string] $Pre, [string] $Build) + $res = [AUVersion]::Parse($Value, $false, 'V1') + $res | Should Not BeNullOrEmpty + $res.Version | Should Be $Ver + $res.Prerelease | Should Be $Pre + $res.BuildMetadata | Should Be $Build + } + + It 'does not parse ""' -TestCases @( + @{Value = '01'} + ) { param([string] $Value) + { [AUVersion]::Parse($Value, $false, 'V1') } | Should Throw 'Invalid SemVer' + } + } + + Context '[AUVersion]::Parse() for a non strict SemVer2 version' { + It 'parses ""' -TestCases @( + @{Value = '01.02' ; Ver = '1.2.0' ; Pre = '' ; Build = ''} + @{Value = 'v01.02' ; Ver = '1.2.0' ; Pre = '' ; Build = ''} + @{Value = '01.02-pre05' ; Ver = '1.2.0' ; Pre = 'pre05'; Build = ''} + @{Value = '01.02+sha06' ; Ver = '1.2.0' ; Pre = '' ; Build = 'sha06'} + @{Value = '01.02-pre05+sha06' ; Ver = '1.2.0' ; Pre = 'pre05'; Build = 'sha06'} + @{Value = '01.02-pre.05+sha.06' ; Ver = '1.2.0' ; Pre = 'pre.5'; Build = 'sha.06'} + @{Value = '01.02.03' ; Ver = '1.2.3' ; Pre = '' ; Build = ''} + @{Value = '01.02.03-pre05+sha06' ; Ver = '1.2.3' ; Pre = 'pre05'; Build = 'sha06'} + @{Value = '01.02.03-pre.05+sha.06' ; Ver = '1.2.3' ; Pre = 'pre.5'; Build = 'sha.06'} + @{Value = '01.02.03.00' ; Ver = '1.2.3' ; Pre = '' ; Build = ''} + @{Value = '01.02.03.00-pre05+sha06' ; Ver = '1.2.3' ; Pre = 'pre05'; Build = 'sha06'} + @{Value = '01.02.03.00-pre.05+sha.06'; Ver = '1.2.3' ; Pre = 'pre.5'; Build = 'sha.06'} + @{Value = '01.02.03.04' ; Ver = '1.2.3.4'; Pre = '' ; Build = ''} + @{Value = '01.02.03.04-pre05+sha06' ; Ver = '1.2.3.4'; Pre = 'pre05'; Build = 'sha06'} + @{Value = '01.02.03.04-pre.05+sha.06'; Ver = '1.2.3.4'; Pre = 'pre.5'; Build = 'sha.06'} + ) { param([string] $Value, [version] $Ver, [string] $Pre, [string] $Build) + $res = [AUVersion]::Parse($Value, $false, 'V2') + $res | Should Not BeNullOrEmpty + $res.Version | Should Be $Ver + $res.Prerelease | Should Be $Pre + $res.BuildMetadata | Should Be $Build + } + + It 'does not parse ""' -TestCases @( + @{Value = '01'} + ) { param([string] $Value) + { [AUVersion]::Parse($Value, $false, 'V2') } | Should Throw 'Invalid SemVer' + } + } + + Context '[AUVersion]::Parse() for a non strict enhanced SemVer2 version' { + It 'parses ""' -TestCases @( + @{Value = '01.02' ; Ver = '1.2.0' ; Pre = '' ; Build = ''} + @{Value = 'v01.02' ; Ver = '1.2.0' ; Pre = '' ; Build = ''} + @{Value = '01.02-pre05' ; Ver = '1.2.0' ; Pre = 'pre.5'; Build = ''} + @{Value = '01.02+sha06' ; Ver = '1.2.0' ; Pre = '' ; Build = 'sha.06'} + @{Value = '01.02-pre05+sha06' ; Ver = '1.2.0' ; Pre = 'pre.5'; Build = 'sha.06'} + @{Value = '01.02-pre.05' ; Ver = '1.2.0' ; Pre = 'pre.5'; Build = ''} + @{Value = '01.02+sha.06' ; Ver = '1.2.0' ; Pre = '' ; Build = 'sha.06'} + @{Value = '01.02-pre.05+sha.06' ; Ver = '1.2.0' ; Pre = 'pre.5'; Build = 'sha.06'} + @{Value = '01.02.03' ; Ver = '1.2.3' ; Pre = '' ; Build = ''} + @{Value = '01.02.03-pre05+sha06' ; Ver = '1.2.3' ; Pre = 'pre.5'; Build = 'sha.06'} + @{Value = '01.02.03-pre.05+sha.06' ; Ver = '1.2.3' ; Pre = 'pre.5'; Build = 'sha.06'} + @{Value = '01.02.03.00' ; Ver = '1.2.3' ; Pre = '' ; Build = ''} + @{Value = '01.02.03.04' ; Ver = '1.2.3.4'; Pre = '' ; Build = ''} + @{Value = '01.02.03.04-pre05+sha06' ; Ver = '1.2.3.4'; Pre = 'pre.5'; Build = 'sha.06'} + @{Value = '01.02.03.04-pre.05+sha.06'; Ver = '1.2.3.4'; Pre = 'pre.5'; Build = 'sha.06'} + ) { param([string] $Value, [version] $Ver, [string] $Pre, [string] $Build) + $res = [AUVersion]::Parse($Value, $false, 'EnhancedV2') + $res | Should Not BeNullOrEmpty + $res.Version | Should Be $Ver + $res.Prerelease | Should Be $Pre + $res.BuildMetadata | Should Be $Build + } + + It 'does not parse ""' -TestCases @( + @{Value = '01'} + ) { param([string] $Value) + { [AUVersion]::Parse($Value, $false, 'EnhancedV2') } | Should Throw 'Invalid SemVer' + } + } + + Context '[AUVersion] conversions (strict SemVer2 version only)' { + It 'converts from [] ""' -TestCases @( + @{Type = 'string' ; Value = '1.2.3'} + @{Type = 'string' ; Value = '1.2.3-pre.4+sha.05'} + @{Type = 'version' ; Value = [version]::Parse('1.2.3')} + @{Type = 'AUVersion'; Value = [AUVersion]::Parse('1.2.3')} + @{Type = 'AUVersion'; Value = [AUVersion]::Parse('1.2.3-pre.4+sha.05')} + @{Type = 'System.Text.RegularExpressions.Capture'; Value = [regex]::Match('1.2.3', '^(.+)$').Groups[1]} + @{Type = 'System.Text.RegularExpressions.Capture'; Value = [regex]::Match('1.2.3-pre.4+sha.05', '^(.+)$').Groups[1]} + ) { param([object] $Value) + $version = [AUVersion] $Value + $version | Should Not BeNullOrEmpty + $version.ToString() | Should Be ($Value -as [string]) + } + + It 'does not convert from [] ""' -TestCases @( + @{Type = 'string' ; Value = '1'} + @{Type = 'string' ; Value = 'v1.2.3'} + @{Type = 'System.Text.RegularExpressions.Capture'; Value = [regex]::Match('1', '^(.+)$').Groups[1]} + @{Type = 'System.Text.RegularExpressions.Capture'; Value = [regex]::Match('v1.2.3', '^(.+)$').Groups[1]} + ) { param([object] $Value) + { [AUVersion] $Value } | Should Throw 'Invalid SemVer' + } + } + + Context '[AUVersion]::ToString()' { + It 'formats ""' -TestCases @( + @{Value = '01.02' ; Result = '1.2.0'} + @{Value = '01.02-pre05' ; Result = '1.2.0-pre05'} + @{Value = '01.02+sha06' ; Result = '1.2.0+sha06'} + @{Value = '01.02-pre05+sha06' ; Result = '1.2.0-pre05+sha06'} + @{Value = '01.02-pre.05' ; Result = '1.2.0-pre.5'} + @{Value = '01.02+sha.06' ; Result = '1.2.0+sha.06'} + @{Value = '01.02-pre.05+sha.06' ; Result = '1.2.0-pre.5+sha.06'} + @{Value = '01.02.03' ; Result = '1.2.3'} + @{Value = '01.02.03-pre05+sha06' ; Result = '1.2.3-pre05+sha06'} + @{Value = '01.02.03-pre.05+sha.06' ; Result = '1.2.3-pre.5+sha.06'} + @{Value = '01.02.03.00' ; Result = '1.2.3'} + @{Value = '01.02.03.04' ; Result = '1.2.3.4'} + @{Value = '01.02.03.04-pre05+sha06' ; Result = '1.2.3.4-pre05+sha06'} + @{Value = '01.02.03.04-pre.05+sha.06'; Result = '1.2.3.4-pre.5+sha.06'} + ) { param([string] $Value, [string] $Result) + $res = [AUVersion] $Value + $res | Should Not BeNullOrEmpty + $res.ToString() | Should Be $Result + $res.ToString(-1) | Should Be $res.Version.ToString() + $res.ToString(2) | Should Be $res.Version.ToString(2) + } + } + + Context '[AUVersion]::CompareTo()' { + It 'compares "" ""' -TestCases @( + @{A = '1.2.3' ; B = '1.2.3' ; Result = '='} + @{A = '1.2.3' ; B = '1.2.33' ; Result = '<'} + @{A = '1.2.3' ; B = '1.2.3.4' ; Result = '<'} + @{A = '1.2.3.4' ; B = '1.2.3.4' ; Result = '='} + @{A = '1.2.3.4' ; B = '1.2.3.44' ; Result = '<'} + @{A = '1.2.3-a1' ; B = '1.2.3-a1' ; Result = '='} + @{A = '1.2.3-a1' ; B = '1.2.3-a2' ; Result = '<'} + @{A = '1.2.3-a2' ; B = '1.2.3-a10' ; Result = '>'} + @{A = '1.2.3-a1' ; B = '1.2.3-b1' ; Result = '<'} + @{A = '1.2.3-a.1'; B = '1.2.3-a.1' ; Result = '='} + @{A = '1.2.3-a.1'; B = '1.2.3-a.2' ; Result = '<'} + @{A = '1.2.3-a.2'; B = '1.2.3-a.10'; Result = '<'} + @{A = '1.2.3-a.1'; B = '1.2.3-b.1' ; Result = '<'} + @{A = '1.2.3+a' ; B = '1.2.3+a' ; Result = '='} + @{A = '1.2.3+a' ; B = '1.2.3+b' ; Result = '='} + ) { param([string] $A, [string] $B, [string] $Result) + $resA = [AUVersion] $A + $resB = [AUVersion] $B + $resA | Should Not BeNullOrEmpty + $resB | Should Not BeNullOrEmpty + if ($Result -eq '=' ) { + $resA | Should Be $resB + } elseif ($Result -eq '<' ) { + $resA | Should BeLessThan $resB + } else { + $resA | Should BeGreaterThan $resB + } + } + } + } +} diff --git a/tests/Get-Version.Tests.ps1 b/tests/Get-Version.Tests.ps1 index 1612199..74c4fd5 100644 --- a/tests/Get-Version.Tests.ps1 +++ b/tests/Get-Version.Tests.ps1 @@ -1,129 +1,75 @@ remove-module AU -ea ignore import-module $PSScriptRoot\..\AU -Describe 'ConvertTo-AUVersion' -Tag getversion { +Describe 'Get-Version' -Tag version { InModuleScope AU { - $testCases = @( - @{Value = '01.02.03.04-beta.01+xyz.01'; ExpectedVersion = '1.2.3.4'; ExpectedPrerelease = 'beta.1'; ExpectedBuildMetadata = 'xyz.01'} - @{Value = '01.02.03-beta01+xyz01' ; ExpectedVersion = '1.2.3' ; ExpectedPrerelease = 'beta01'; ExpectedBuildMetadata = 'xyz01'} - # The following test cases are intended to match chocolatey normalized versions - @{Value = '01.02-beta+xyz' ; ExpectedVersion = '1.2.0' ; ExpectedPrerelease = 'beta' ; ExpectedBuildMetadata = 'xyz'} - @{Value = '01.02.03.00-beta+xyz' ; ExpectedVersion = '1.2.3' ; ExpectedPrerelease = 'beta' ; ExpectedBuildMetadata = 'xyz'} - ) - - It 'should convert a strict version: ' -TestCases $testCases { param([string] $Value, [version] $ExpectedVersion, [string] $ExpectedPrerelease, [string] $ExpectedBuildMetadata) - $res = ConvertTo-AUVersion $Value - $res | Should Not BeNullOrEmpty - $res.Version | Should Be $ExpectedVersion - $res.Prerelease | Should BeExactly $ExpectedPrerelease - $res.BuildMetadata | Should BeExactly $ExpectedBuildMetadata - $res.ToString() | Should BeExactly "$ExpectedVersion-$ExpectedPrerelease+$ExpectedBuildMetadata" - $res.ToString(2) | Should BeExactly $ExpectedVersion.ToString(2) - $res.ToString(-1) | Should BeExactly $ExpectedVersion.ToString() - } - - $testCases = @( - @{Value = '1.2.3.4a'} - @{Value = 'v1.2.3.4-beta.1+xyz.01'} - ) - - It 'should not convert a non strict version: ' -TestCases $testCases { param([string] $Value) - { ConvertTo-AUVersion $Value } | Should Throw - } - - $testCases = @( - @{A = '1.9.0' ; B = '1.9.0' ; ExpectedResult = '='} - @{A = '1.9.0' ; B = '1.10.0' ; ExpectedResult = '<'} - @{A = '1.10.0' ; B = '1.11.0' ; ExpectedResult = '<'} - @{A = '1.0.0' ; B = '2.0.0' ; ExpectedResult = '<'} - @{A = '2.0.0' ; B = '2.1.0' ; ExpectedResult = '<'} - @{A = '2.1.0' ; B = '2.1.1' ; ExpectedResult = '<'} - @{A = '1.0.0-alpha' ; B = '1.0.0-alpha' ; ExpectedResult = '='} - @{A = '1.0.0-alpha' ; B = '1.0.0' ; ExpectedResult = '<'} - @{A = '1.0.0-alpha.1' ; B = '1.0.0-alpha.1' ; ExpectedResult = '='} - @{A = '1.0.0-alpha.1' ; B = '1.0.0-alpha.01' ; ExpectedResult = '='} - @{A = '1.0.0-alpha' ; B = '1.0.0-alpha.1' ; ExpectedResult = '<'} - @{A = '1.0.0-alpha.1' ; B = '1.0.0-alpha.beta'; ExpectedResult = '<'} - @{A = '1.0.0-alpha.beta'; B = '1.0.0-beta' ; ExpectedResult = '<'} - @{A = '1.0.0-beta' ; B = '1.0.0-beta.2' ; ExpectedResult = '<'} - @{A = '1.0.0-beta.2' ; B = '1.0.0-beta.11' ; ExpectedResult = '<'} - @{A = '1.0.0-beta.11' ; B = '1.0.0-rc.1' ; ExpectedResult = '<'} - @{A = '1.0.0-rc.1' ; B = '1.0.0' ; ExpectedResult = '<'} - @{A = '1.0.0' ; B = '1.0.0+1' ; ExpectedResult = '='} - @{A = '1.0.0+1' ; B = '1.0.0+2' ; ExpectedResult = '='} - @{A = '1.0.0-alpha' ; B = '1.0.0-alpha+1' ; ExpectedResult = '='} - @{A = '1.0.0-alpha+1' ; B = '1.0.0-alpha+2' ; ExpectedResult = '='} - ) - - It 'should compare 2 versions successfully: ' -TestCases $testCases { param([string] $A, [string] $B, [string] $ExpectedResult) - $VersionA = ConvertTo-AUVersion $A - $VersionB = ConvertTo-AUVersion $B - if ($ExpectedResult -eq '>' ) { - $VersionA | Should BeGreaterThan $VersionB - } elseif ($ExpectedResult -eq '<' ) { - $VersionA | Should BeLessThan $VersionB - } else { - $VersionA | Should Be $VersionB + Context 'Get-Version [-SemVer V1]' { + It 'parses ""' -TestCases @( + @{Result = '4.28.0' ; Value = '4.28'} + @{Result = '2.5.0' ; Value = 'v2.5.0'} + @{Result = '4.0.3-Beta1' ; Value = '4.0.3Beta1'} + @{Result = '4.0.3-Beta1' ; Value = '4.0.3Beta.1'} + @{Result = '8.0.0-rc1' ; Value = 'v8.0-rc1'} + @{Result = '8.0.0-rc1' ; Value = 'v8.0-rc.1'} + @{Result = '1.61.0-beta0' ; Value = 'v1.61.0-beta0'} + @{Result = '1.61.0-beta0' ; Value = 'v1.61.0-beta.0'} + @{Result = '1.79.2.23166' ; Value = '1.79.2.23166'} + @{Result = '2.1.1-beta2' ; Value = 'Current version 2.1.1 beta 2.'} + @{Result = '2.1.1-beta2' ; Value = 'Current version 2.1.1 beta.2.'} + @{Result = '5.6.3-x86msi' ; Value = 'https://dl.airserver.com/pc32/AirServer-5.6.3-x86.msi'} + @{Result = '5.6.3' ; Delimiter = '-'; Value = 'https://dl.airserver.com/pc32/AirServer-5.6.3-x86.msi'} + @{Result = '5.32.1.1' ; Value = 'https://strawberryperl.com/download/5.32.1.1/strawberry-perl-5.32.1.1-32bit.msi'} + ) { param([string] $Value, [string] $Delimiter, [string] $Result) + $res = Get-Version $Value -Delimiter $Delimiter + $res | Should Not BeNullOrEmpty + $res.ToString() | Should Be $Result } } - } -} - -Describe 'Get-Version' -Tag getversion { - InModuleScope AU { - $testCases = @( - @{Value = 'v01.02.03.04beta.01+xyz.01'; ExpectedVersion = '1.2.3.4'; ExpectedPrerelease = 'beta.1'; ExpectedBuildMetadata = 'xyz.01'} - @{Value = 'v01.02.03 beta 01 xyz 01 z'; ExpectedVersion = '1.2.3' ; ExpectedPrerelease = 'beta.1'; ExpectedBuildMetadata = 'xyz.01'} - @{Value = 'v01.02.03 beta01 xyz01' ; ExpectedVersion = '1.2.3' ; ExpectedPrerelease = 'beta.1'; ExpectedBuildMetadata = 'xyz.01'} - ) - - It 'should parse a non strict version: ' -TestCases $testCases { param([string] $Value, [version] $ExpectedVersion, [string] $ExpectedPrerelease, [string] $ExpectedBuildMetadata) - $res = Get-Version $Value - $res | Should Not BeNullOrEmpty - $res.Version | Should Be $ExpectedVersion - $res.Prerelease | Should BeExactly $ExpectedPrerelease - $res.BuildMetadata | Should BeExactly $ExpectedBuildMetadata - } - $testCases = @( - @{ExpectedResult = '5.4.9' ; Delimiter = '-' ; Value = 'http://dl.airserver.com/pc32/AirServer-5.4.9-x86.msi'} - @{ExpectedResult = '1.24.0-beta.2' ; Value = 'https://github.com/atom/atom/releases/download/v1.24.0-beta2/AtomSetup.exe'} - @{ExpectedResult = '2.4.0.24-beta' ; Value = 'https://github.com/gurnec/HashCheck/releases/download/v2.4.0.24-beta/HashCheckSetup-v2.4.0.24-beta.exe'} - @{ExpectedResult = '2.0.9' ; Value = 'http://www.ltr-data.se/files/imdiskinst_2.0.9.exe'} - @{ExpectedResult = '17.6' ; Delimiter = '-' ; Value = 'http://mirrors.kodi.tv/releases/windows/win32/kodi-17.6-Krypton-x86.exe'} - @{ExpectedResult = '0.70.2' ; Value = 'https://github.com/Nevcairiel/LAVFilters/releases/download/0.70.2/LAVFilters-0.70.2-Installer.exe'} - @{ExpectedResult = '2.2.0-1' ; Value = 'https://files.kde.org/marble/downloads/windows/Marble-setup_2.2.0-1_x64.exe'} - @{ExpectedResult = '2.3.2' ; Value = 'https://github.com/sabnzbd/sabnzbd/releases/download/2.3.2/SABnzbd-2.3.2-win-setup.exe'} - @{ExpectedResult = '1.9' ; Delimiter = '-' ; Value = 'http://download.serviio.org/releases/serviio-1.9-win-setup.exe'} - @{ExpectedResult = '0.17.0' ; Value = 'https://github.com/Stellarium/stellarium/releases/download/v0.17.0/stellarium-0.17.0-win32.exe'} - @{ExpectedResult = '5.24.3.1' ; Value = 'http://strawberryperl.com/download/5.24.3.1/strawberry-perl-5.24.3.1-32bit.msi'} - @{ExpectedResult = '3.5.4' ; Value = 'https://github.com/SubtitleEdit/subtitleedit/releases/download/3.5.4/SubtitleEdit-3.5.4-Setup.zip'} - @{ExpectedResult = '1.2.3-beta.4' ; Value = 'v 1.2.3 beta 4'} - @{ExpectedResult = '1.2.3-beta.3' ; Value = 'Last version: 1.2.3 beta 3.'} - ) - - It 'should parse any non strict version: ' -TestCases $testCases { param($Value, $Delimiter, $ExpectedResult) - $version = Get-Version $Value -Delimiter $Delimiter - $version | Should Be ([AUVersion] $ExpectedResult) + Context 'Get-Version -SemVer V2' { + It 'parses ""' -TestCases @( + @{Result = '4.28.0' ; Value = '4.28'} + @{Result = '2.5.0' ; Value = 'v2.5.0'} + @{Result = '4.0.3-Beta1' ; Value = '4.0.3Beta1'} + @{Result = '4.0.3-Beta.1' ; Value = '4.0.3Beta.1'} + @{Result = '8.0.0-rc1' ; Value = 'v8.0-rc1'} + @{Result = '8.0.0-rc.1' ; Value = 'v8.0-rc.1'} + @{Result = '1.61.0-beta0' ; Value = 'v1.61.0-beta0'} + @{Result = '1.61.0-beta.0' ; Value = 'v1.61.0-beta.0'} + @{Result = '1.79.2.23166' ; Value = '1.79.2.23166'} + @{Result = '2.1.1-beta.2' ; Value = 'Current version 2.1.1 beta 2.'} + @{Result = '2.1.1-beta.2' ; Value = 'Current version 2.1.1 beta.2.'} + @{Result = '5.6.3-x86.msi' ; Value = 'https://dl.airserver.com/pc32/AirServer-5.6.3-x86.msi'} + @{Result = '5.6.3' ; Delimiter = '-'; Value = 'https://dl.airserver.com/pc32/AirServer-5.6.3-x86.msi'} + @{Result = '5.32.1.1' ; Value = 'https://strawberryperl.com/download/5.32.1.1/strawberry-perl-5.32.1.1-32bit.msi'} + ) { param([string] $Value, [string] $Delimiter, [string] $Result) + $res = Get-Version -SemVer V2 $Value -Delimiter $Delimiter + $res | Should Not BeNullOrEmpty + $res.ToString() | Should Be $Result + } } - } -} -Describe '[AUVersion]' -Tag getversion { - InModuleScope AU { - $testCases = @( - @{Type = 'string' ; Value = '1.2'} - @{Type = 'string' ; Value = '1.2-beta+03'} - @{Type = 'AUVersion'; Value = [AUVersion] '1.2'} - @{Type = 'AUVersion'; Value = [AUVersion] '1.2-beta+03'} - @{Type = 'version' ; Value = [version] '1.2'} - @{Type = 'System.Text.RegularExpressions.Capture'; Value = [regex]::Match('1.2', '^(.+)$').Groups[1]} - @{Type = 'System.Text.RegularExpressions.Capture'; Value = [regex]::Match('1.2-beta+03', '^(.+)$').Groups[1]} - ) - - It 'converts from: [] ' -TestCases $testCases { param($Value) - $version = [AUVersion] $Value - $version | Should Not BeNullOrEmpty + Context 'Get-Version -SemVer EnhancedV2' { + It 'parses ""' -TestCases @( + @{Result = '4.28.0' ; Value = '4.28'} + @{Result = '2.5.0' ; Value = 'v2.5.0'} + @{Result = '4.0.3-Beta.1' ; Value = '4.0.3Beta1'} + @{Result = '4.0.3-Beta.1' ; Value = '4.0.3Beta.1'} + @{Result = '8.0.0-rc.1' ; Value = 'v8.0-rc1'} + @{Result = '8.0.0-rc.1' ; Value = 'v8.0-rc.1'} + @{Result = '1.61.0-beta.0' ; Value = 'v1.61.0-beta0'} + @{Result = '1.61.0-beta.0' ; Value = 'v1.61.0-beta.0'} + @{Result = '1.79.2.23166' ; Value = '1.79.2.23166'} + @{Result = '2.1.1-beta.2' ; Value = 'Current version 2.1.1 beta 2.'} + @{Result = '2.1.1-beta.2' ; Value = 'Current version 2.1.1 beta.2.'} + @{Result = '5.6.3-x86.msi' ; Value = 'https://dl.airserver.com/pc32/AirServer-5.6.3-x86.msi'} + @{Result = '5.6.3' ; Delimiter = '-'; Value = 'https://dl.airserver.com/pc32/AirServer-5.6.3-x86.msi'} + @{Result = '5.32.1.1' ; Value = 'https://strawberryperl.com/download/5.32.1.1/strawberry-perl-5.32.1.1-32bit.msi'} + ) { param([string] $Value, [string] $Delimiter, [string] $Result) + $res = Get-Version -SemVer EnhancedV2 $Value -Delimiter $Delimiter + $res | Should Not BeNullOrEmpty + $res.ToString() | Should Be $Result + } } } }