diff --git a/README.md b/README.md index d8a3009..610be48 100644 --- a/README.md +++ b/README.md @@ -343,15 +343,15 @@ 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 rc1 -PS> $version = Get-Version '1.3.2-beta2+5' +PS> $version = Get-Version -SemVer V2 '1.3.2-beta.2+5' PS> $version.ToString(2) + ' => ' + $version.ToString() -1.3 => 1.3.2-beta2+5 +1.3 => 1.3.2-beta.2+5 ``` ### WhatIf diff --git a/src/Private/AUVersion.ps1 b/src/Private/AUVersion.ps1 index 8b91361..b32182a 100644 --- a/src/Private/AUVersion.ps1 +++ b/src/Private/AUVersion.ps1 @@ -1,71 +1,131 @@ +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 = $version - $this.Prerelease = $prerelease + $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] $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 [AUVersion] Parse([string] $value, [bool] $strict) { + return [AUVersion]::Parse($value, $strict, [SemVer]::V2) } - static [bool] TryParse([string] $input, [ref] $result) { return [AUVersion]::TryParse($input, $result, $true) } + 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 [bool] TryParse([string] $value, [ref] $result) { + return [AUVersion]::TryParse($value, $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 = $pr.Replace(' ', '.') } - if ($bm -and !$strict) { $bm = $bm.Replace(' ', '.') } - # for now, chocolatey does only support SemVer v1 (no dot separated identifiers in pre-release): - if ($pr -and $strict -and $pr -like '*.*') { return $false } - if ($bm -and $strict -and $bm -like '*.*') { return $false } - if ($pr) { $pr = $pr.Replace('.', '') } - if ($bm) { $bm = $bm.Replace('.', '') } - # - $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.Build -eq -1) { + return [version] "$value.0" + } + if ($value.Revision -eq 0) { + return [version] $value.ToString(3) + } + return $value + } + + hidden static [object[]] NormalizePrerelease([string] $value) { + $result = @() + if ($value) { + $value -split '\.' | ForEach-Object { + # if identifier is exclusively numeric, cast it to an int + if ($_ -match '^\d+$') { + $result += [int] $_ + } else { + $result += $_ + } + } + } + return $result + } + hidden static [string] GetPattern([bool] $strict) { $versionPattern = '(?\d+(?:\.\d+){1,3})' if ($strict) { $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 [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 + } } + $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())" } + 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++) { @@ -89,8 +149,8 @@ class AUVersion : System.IComparable { [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 } @@ -100,17 +160,8 @@ class AUVersion : System.IComparable { } hidden [object[]] GetParts() { - $result = @($this.Version) - if ($this.Prerelease) { - $this.Prerelease -split '\.' | ForEach-Object { - # if identifier is exclusively numeric, cast it to an int - if ($_ -match '^[0-9]+$') { - $result += [int] $_ - } else { - $result += $_ - } - } - } + $result = , $this.Version + $result += [AUVersion]::NormalizePrerelease($this.Prerelease) return $result } } diff --git a/src/Public/Get-Version.ps1 b/src/Public/Get-Version.ps1 index e60d3f8..8764926 100644 --- a/src/Public/Get-Version.ps1 +++ b/src/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-beta3 + 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-beta2 + 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/tests/AUVersion.Tests.ps1 b/tests/AUVersion.Tests.ps1 new file mode 100644 index 0000000..2460b20 --- /dev/null +++ b/tests/AUVersion.Tests.ps1 @@ -0,0 +1,280 @@ +remove-module Chocolatey-AU -ea ignore +import-module $PSScriptRoot\..\Chocolatey-AU\Chocolatey-AU.psm1 # Tests require the private functions exported -force + +Describe '[AUVersion]' -Tag version { + InModuleScope Chocolatey-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 56932e7..00ae06d 100644 --- a/tests/Get-Version.Tests.ps1 +++ b/tests/Get-Version.Tests.ps1 @@ -1,144 +1,75 @@ remove-module Chocolatey-AU -ea ignore import-module $PSScriptRoot\..\Chocolatey-AU\Chocolatey-AU.psm1 # Tests require the private functions exported -Describe 'Get-Version' -Tag getversion { +Describe 'Get-Version' -Tag version { InModuleScope Chocolatey-AU { - It 'should convert a strict version' { - $expectedVersionStart = '1.2' - $expectedVersion = "$expectedVersionStart.3.4" - # for now, chocolatey does only support SemVer v1 (no dot separated identifiers in pre-release): - $expectedPrerelease = 'beta1' - $expectedBuildMetadata = 'xyz001' - # here is the SemVer v2 equivalent: - #$expectedPrerelease = 'beta.1' - #$expectedBuildMetadata = 'xyz.001' - $expected = "$expectedVersion-$expectedPrerelease+$expectedBuildMetadata" - $res = ConvertTo-AUVersion $expected - - $res | Should Not BeNullOrEmpty - $res.Version | Should Be ([version] $expectedVersion) - $res.Prerelease | Should BeExactly $expectedPrerelease - $res.BuildMetadata | Should BeExactly $expectedBuildMetadata - $res.ToString() | Should BeExactly $expected - $res.ToString(2) | Should BeExactly $expectedVersionStart - $res.ToString(-1) | Should BeExactly $expectedVersion - } - - It 'should not convert a non-strict version' { - { ConvertTo-AUVersion '1.2.3.4a' } | Should Throw - { ConvertTo-AUVersion 'v1.2.3.4-beta.1+xyz.001' } | Should Throw - } - - It 'should parse a non strict version' { - $expectedVersion = "1.2.3.4" - # for now, chocolatey does only support SemVer v1 (no dot separated identifiers in pre-release): - $expectedPrerelease = 'beta1' - $expectedBuildMetadata = 'xyz001' - # here is the SemVer v2 equivalent: - #$expectedPrerelease = 'beta.1' - #$expectedBuildMetadata = 'xyz.001' - $res = Get-Version "v$expectedVersion$expectedPrerelease+$expectedBuildMetadata" - - $res | Should Not BeNullOrEmpty - $res.Version | Should Be ([version] $expectedVersion) - $res.Prerelease | Should BeExactly $expectedPrerelease - $res.BuildMetadata | Should BeExactly $expectedBuildMetadata - } - - $testCases = @( - @{A = '1.9.0' ; B = '1.9.0' ; ExpectedResult = 0} - @{A = '1.9.0' ; B = '1.10.0' ; ExpectedResult = -1} - @{A = '1.10.0' ; B = '1.11.0' ; ExpectedResult = -1} - @{A = '1.0.0' ; B = '2.0.0' ; ExpectedResult = -1} - @{A = '2.0.0' ; B = '2.1.0' ; ExpectedResult = -1} - @{A = '2.1.0' ; B = '2.1.1' ; ExpectedResult = -1} - @{A = '1.0.0-alpha' ; B = '1.0.0-alpha' ; ExpectedResult = 0} - @{A = '1.0.0-alpha' ; B = '1.0.0' ; ExpectedResult = -1} - # for now, chocolatey does only support SemVer v1 (no dot separated identifiers in pre-release): - @{A = '1.0.0-alpha1' ; B = '1.0.0-alpha1' ; ExpectedResult = 0} - @{A = '1.0.0-alpha' ; B = '1.0.0-alpha1' ; ExpectedResult = -1} - @{A = '1.0.0-alpha1' ; B = '1.0.0-alphabeta' ; ExpectedResult = -1} - @{A = '1.0.0-alphabeta' ; B = '1.0.0-beta' ; ExpectedResult = -1} - @{A = '1.0.0-beta' ; B = '1.0.0-beta2' ; ExpectedResult = -1} - @{A = '1.0.0-beta2' ; B = '1.0.0-rc1' ; ExpectedResult = -1} - @{A = '1.0.0-rc1' ; B = '1.0.0' ; ExpectedResult = -1} - # here is the SemVer v2 equivalent: - #@{A = '1.0.0-alpha.1' ; B = '1.0.0-alpha.1' ; ExpectedResult = 0} - #@{A = '1.0.0-alpha.1' ; B = '1.0.0-alpha.01' ; ExpectedResult = 0} - #@{A = '1.0.0-alpha' ; B = '1.0.0-alpha.1' ; ExpectedResult = -1} - #@{A = '1.0.0-alpha.1' ; B = '1.0.0-alpha.beta'; ExpectedResult = -1} - #@{A = '1.0.0-alpha.beta'; B = '1.0.0-beta' ; ExpectedResult = -1} - #@{A = '1.0.0-beta' ; B = '1.0.0-beta.2' ; ExpectedResult = -1} - #@{A = '1.0.0-beta.2' ; B = '1.0.0-beta.11' ; ExpectedResult = -1} - #@{A = '1.0.0-beta.11' ; B = '1.0.0-rc.1' ; ExpectedResult = -1} - #@{A = '1.0.0-rc.1' ; B = '1.0.0' ; ExpectedResult = -1} - @{A = '1.0.0' ; B = '1.0.0+1' ; ExpectedResult = 0} - @{A = '1.0.0+1' ; B = '1.0.0+2' ; ExpectedResult = 0} - @{A = '1.0.0-alpha' ; B = '1.0.0-alpha+1' ; ExpectedResult = 0} - @{A = '1.0.0-alpha+1' ; B = '1.0.0-alpha+2' ; ExpectedResult = 0} - ) - - It 'should compare 2 versions successfully' -TestCases $testCases { param([string] $A, [string] $B, [int] $ExpectedResult) - $VersionA = ConvertTo-AUVersion $A - $VersionB = ConvertTo-AUVersion $B - if ($ExpectedResult -gt 0 ) { - $VersionA | Should BeGreaterThan $VersionB - } elseif ($ExpectedResult -lt 0 ) { - $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 } } - $testCases = @( - @{Value = '1.2'} - @{Value = '1.2-beta+003'} - @{Value = [AUVersion] '1.2'} - @{Value = [AUVersion] '1.2-beta+003'} - @{Value = [version] '1.2'} - @{Value = [regex]::Match('1.2', '^(.+)$').Groups[1]} - @{Value = [regex]::Match('1.2-beta+003', '^(.+)$').Groups[1]} - ) - - It 'converts from any type of values' -TestCases $testCases { param($Value) - $version = [AUVersion] $Value - $version | Should Not BeNullOrEmpty - } - - $testCases = @( - @{Value = '1.2-beta.3'} - @{Value = '1.2+xyz.4'} - @{Value = '1.2-beta.3+xyz.4'} - ) - - It 'does not convert semver v2' -TestCases $testCases { param($Value, $ExpectedResult) - { [AUVersion] $Value } | Should Throw 'Invalid version' + 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 + } } - $testCases = @( - @{ExpectedResult = '5.4.9' ; Delimiter = '-' ; Value = 'http://dl.airserver.com/pc32/AirServer-5.4.9-x86.msi'} - @{ExpectedResult = '1.24.0-beta2' ; 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'} - # for now, chocolatey does only support SemVer v1 (no dot separated identifiers in pre-release): - @{ExpectedResult = '1.2.3-beta4' ; Value = 'v 1.2.3 beta 4'} - @{ExpectedResult = '1.2.3-beta3' ; Value = 'Last version: 1.2.3 beta 3.'} - # here is the SemVer v2 equivalent: - #@{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 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 + } } } } diff --git a/tests/Update-Package.Tests.ps1 b/tests/Update-Package.Tests.ps1 index 6be4a49..24b5a34 100644 --- a/tests/Update-Package.Tests.ps1 +++ b/tests/Update-Package.Tests.ps1 @@ -175,7 +175,8 @@ Describe 'Update-Package' -Tag update { $res.Updated | Should Be $true get_latest -Version 1.3-alpha.1 - { update } | Should Throw "Invalid version" + $res = update + $res.Updated | Should Be $true get_latest -Version 1.3a { update } | Should Throw "Invalid version"