Skip to content

Commit

Permalink
Fix [semver] type to pass semver.org tests (#21401)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
SteveL-MSFT and Steve Lee (POWERSHELL HE/HIM) (from Dev Box) committed Apr 22, 2024
1 parent b968e10 commit 4bcc3f4
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 14 deletions.
29 changes: 15 additions & 14 deletions src/System.Management.Automation/engine/PSVersionInfo.cs
Expand Up @@ -335,8 +335,9 @@ IEnumerator IEnumerable.GetEnumerator()
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";
Expand Down Expand Up @@ -370,7 +371,7 @@ public SemanticVersion(string version)
/// <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)
Expand All @@ -387,7 +388,7 @@ public SemanticVersion(int major, int minor, int patch, string preReleaseLabel,

if (!string.IsNullOrEmpty(buildLabel))
{
if (!Regex.IsMatch(buildLabel, LabelUnitRegEx))
if (!Regex.IsMatch(buildLabel, BuildUnitRegEx))
{
throw new FormatException(nameof(buildLabel));
}
Expand All @@ -410,7 +411,7 @@ public SemanticVersion(int major, int minor, int patch, string preReleaseLabel,
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))
Expand Down Expand Up @@ -447,7 +448,7 @@ public SemanticVersion(int major, int minor, int patch)
throw PSTraceSource.NewArgumentException(nameof(minor));
}

if (patch < 0)
if (patch < 0)
{
throw PSTraceSource.NewArgumentException(nameof(patch));
}
Expand Down Expand Up @@ -568,12 +569,12 @@ public SemanticVersion(Version version)
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; }

Expand Down Expand Up @@ -643,7 +644,7 @@ private static bool TryParseVersion(string version, ref VersionResult result)
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('+');

Expand Down Expand Up @@ -729,7 +730,7 @@ private static bool TryParseVersion(string version, ref VersionResult result)
}

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;
Expand Down Expand Up @@ -804,7 +805,7 @@ public int CompareTo(object version)

/// <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)
{
Expand All @@ -820,7 +821,7 @@ public int CompareTo(SemanticVersion value)
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);
}

Expand All @@ -837,7 +838,7 @@ public override bool Equals(object obj)
/// </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);
Expand Down Expand Up @@ -906,7 +907,7 @@ public override int GetHashCode()

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:
Expand Down
101 changes: 101 additions & 0 deletions test/powershell/engine/Basic/SemanticVersion.Tests.ps1
Expand Up @@ -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
}
}

}

0 comments on commit 4bcc3f4

Please sign in to comment.