Fix [semver] type to pass semver.org tests (#21401)

* Fix `[semver]` type to pass semver.org tests

* fix test to split per OS specific newline

* replace all regex with semver.org ones adding new one for build label, update tests

---------

Co-authored-by: Steve Lee (POWERSHELL HE/HIM) (from Dev Box) <slee@ntdev.microsoft.com>
This commit is contained in:
Steve Lee
2024-04-22 16:32:22 -04:00
committed by GitHub
co-authored by Steve Lee
parent b968e101b9
commit 4bcc3f41c0
2 changed files with 116 additions and 14 deletions
@@ -335,8 +335,9 @@ namespace System.Management.Automation
public sealed class SemanticVersion : IComparable, IComparable<SemanticVersion>, IEquatable<SemanticVersion>
{
private const string VersionSansRegEx = @"^(?<major>\d+)(\.(?<minor>\d+))?(\.(?<patch>\d+))?$";
private const string LabelRegEx = @"^((?<preLabel>[0-9A-Za-z][0-9A-Za-z\-\.]*))?(\+(?<buildLabel>[0-9A-Za-z][0-9A-Za-z\-\.]*))?$";
private const string LabelUnitRegEx = @"^[0-9A-Za-z][0-9A-Za-z\-\.]*$";
private const string LabelRegEx = @"^(?<preLabel>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(?:\+(?<buildLabel>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$";
private const string LabelUnitRegEx = @"^((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)$";
private const string BuildUnitRegEx = @"^([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)$";
private const string PreLabelPropertyName = "PSSemVerPreReleaseLabel";
private const string BuildLabelPropertyName = "PSSemVerBuildLabel";
private const string TypeNameForVersionWithLabel = "System.Version#IncludeLabel";
@@ -370,7 +371,7 @@ namespace System.Management.Automation
/// <param name="buildLabel">The build metadata for the version.</param>
/// <exception cref="FormatException">
/// If <paramref name="preReleaseLabel"/> don't match 'LabelUnitRegEx'.
/// If <paramref name="buildLabel"/> don't match 'LabelUnitRegEx'.
/// If <paramref name="buildLabel"/> don't match 'BuildUnitRegEx'.
/// </exception>
public SemanticVersion(int major, int minor, int patch, string preReleaseLabel, string buildLabel)
: this(major, minor, patch)
@@ -387,7 +388,7 @@ namespace System.Management.Automation
if (!string.IsNullOrEmpty(buildLabel))
{
if (!Regex.IsMatch(buildLabel, LabelUnitRegEx))
if (!Regex.IsMatch(buildLabel, BuildUnitRegEx))
{
throw new FormatException(nameof(buildLabel));
}
@@ -410,7 +411,7 @@ namespace System.Management.Automation
public SemanticVersion(int major, int minor, int patch, string label)
: this(major, minor, patch)
{
// We presume the SymVer :
// We presume the SemVer :
// 1) major.minor.patch-label
// 2) 'label' starts with letter or digit.
if (!string.IsNullOrEmpty(label))
@@ -447,7 +448,7 @@ namespace System.Management.Automation
throw PSTraceSource.NewArgumentException(nameof(minor));
}
if (patch < 0)
if (patch < 0)
{
throw PSTraceSource.NewArgumentException(nameof(patch));
}
@@ -568,12 +569,12 @@ namespace System.Management.Automation
public int Patch { get; }
/// <summary>
/// PreReleaseLabel position in the SymVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'.
/// PreReleaseLabel position in the SemVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'.
/// </summary>
public string PreReleaseLabel { get; }
/// <summary>
/// BuildLabel position in the SymVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'.
/// BuildLabel position in the SemVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'.
/// </summary>
public string BuildLabel { get; }
@@ -643,7 +644,7 @@ namespace System.Management.Automation
string preLabel = null;
string buildLabel = null;
// We parse the SymVer 'version' string 'major.minor.patch-PreReleaseLabel+BuildLabel'.
// We parse the SemVer 'version' string 'major.minor.patch-PreReleaseLabel+BuildLabel'.
var dashIndex = version.IndexOf('-');
var plusIndex = version.IndexOf('+');
@@ -729,7 +730,7 @@ namespace System.Management.Automation
}
if (preLabel != null && !Regex.IsMatch(preLabel, LabelUnitRegEx) ||
(buildLabel != null && !Regex.IsMatch(buildLabel, LabelUnitRegEx)))
(buildLabel != null && !Regex.IsMatch(buildLabel, BuildUnitRegEx)))
{
result.SetFailure(ParseFailureKind.FormatException);
return false;
@@ -804,7 +805,7 @@ namespace System.Management.Automation
/// <summary>
/// Implement <see cref="IComparable{T}.CompareTo"/>.
/// Meets SymVer 2.0 p.11 https://semver.org/
/// Meets SemVer 2.0 p.11 https://semver.org/
/// </summary>
public int CompareTo(SemanticVersion value)
{
@@ -820,7 +821,7 @@ namespace System.Management.Automation
if (Patch != value.Patch)
return Patch > value.Patch ? 1 : -1;
// SymVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata).
// SemVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata).
return ComparePreLabel(this.PreReleaseLabel, value.PreReleaseLabel);
}
@@ -837,7 +838,7 @@ namespace System.Management.Automation
/// </summary>
public bool Equals(SemanticVersion other)
{
// SymVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata).
// SemVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata).
return other != null &&
(Major == other.Major) && (Minor == other.Minor) && (Patch == other.Patch) &&
string.Equals(PreReleaseLabel, other.PreReleaseLabel, StringComparison.Ordinal);
@@ -906,7 +907,7 @@ namespace System.Management.Automation
private static int ComparePreLabel(string preLabel1, string preLabel2)
{
// Symver 2.0 standard p.9
// SemVer 2.0 standard p.9
// Pre-release versions have a lower precedence than the associated normal version.
// Comparing each dot separated identifier from left to right
// until a difference is found as follows:
@@ -267,4 +267,105 @@ Describe "SemanticVersion api tests" -Tags 'CI' {
{ $PSVersionTable.PSVersion | Format-Table | Out-String } | Should -Not -Throw
}
}
Context 'Semver official tests' {
BeforeAll {
$valid = @'
0.0.4
1.2.3
10.20.30
1.1.2-prerelease+meta
1.1.2+meta
1.1.2+meta-valid
1.0.0-alpha
1.0.0-beta
1.0.0-alpha.beta
1.0.0-alpha.beta.1
1.0.0-alpha.1
1.0.0-alpha0.valid
1.0.0-alpha.0valid
1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay
1.0.0-rc.1+build.1
2.0.0-rc.1+build.123
1.2.3-beta
10.2.3-DEV-SNAPSHOT
1.2.3-SNAPSHOT-123
1.0.0
2.0.0
1.1.7
2.0.0+build.1848
2.0.1-alpha.1227
1.0.0-alpha+beta
1.2.3----RC-SNAPSHOT.12.9.1--.12+788
1.2.3----R-S.12.9.1--.12+meta
1.2.3----RC-SNAPSHOT.12.9.1--.12
1.0.0+0.build.1-rc.10000aaa-kk-0.1
1.0.0-0A.is.legal
'@
$validVersions = @()
foreach ($version in $valid.Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries)) {
$validVersions += @{version = $version}
}
$invalid = @'
1
1.2
1.2.3-0123
1.2.3-0123.0123
1.1.2+.123
+invalid
-invalid
-invalid+invalid
-invalid.01
alpha
alpha.beta
alpha.beta.1
alpha.1
alpha+beta
alpha_beta
alpha.
alpha..
beta
1.0.0-alpha_beta
-alpha.
1.0.0-alpha..
1.0.0-alpha..1
1.0.0-alpha...1
1.0.0-alpha....1
1.0.0-alpha.....1
1.0.0-alpha......1
1.0.0-alpha.......1
01.1.1
1.01.1
1.1.01
1.2
1.2.3.DEV
1.2-SNAPSHOT
1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788
1.2-RC-SNAPSHOT
-1.0.3-gamma+b7718
+justmeta
9.8.7+meta+meta
9.8.7-whatever+meta+meta
99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12
'@
$invalidVersions = @()
foreach ($version in $invalid.Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries)) {
$invalidVersions += @{version = $version}
}
}
It 'Should parse valid versions: <version>' -TestCases $validVersions {
param($version)
$v = [semver]"$version"
$v.ToString() | Should -Be $version
}
It 'Should not parse invalid versions: <version>' -TestCases $invalidVersions {
{ [semver]"$version" } | Should -Throw
}
}
}