Consider DBNull.Value and NullString.Value the same as $null when comparing with $null and casting to bool (#9794)

- Adds `LanguagePrimitives.IsNullLike()` method to account for `DBNull.Value` and `NullString.Value` so that they can be considered the same as a null value where sensible in PowerShell.
- Updates `-ne` and `-eq` binders to treat `DBNull.Value` and `NullString.Value` as equal to null/AutomationNull.
- Update code paths for comparing objects in LanguagePrimitives to ensure consistency with how the `-eq` and `-ne` binders work when calling LanguagePrimitives methods to do the comparisons.
- Make `LanguagePrimitives.IsNull()` and `LanguagePrimitives.IsNullLike()` public methods.
- Added tests for null behaviours in `NullRepresentatives.Tests.ps1`
This commit is contained in:
Joel Sallow (/u/ta11ow) 2019-06-28 14:39:34 -04:00 committed by Dongbo Wang
parent f3a3922285
commit b34e331d63
4 changed files with 209 additions and 105 deletions

View file

@ -593,9 +593,7 @@ namespace System.Management.Automation
/// <param name="second">Object to compare first to.</param> /// <param name="second">Object to compare first to.</param>
/// <returns>True if first is equal to the second.</returns> /// <returns>True if first is equal to the second.</returns>
public static new bool Equals(object first, object second) public static new bool Equals(object first, object second)
{ => Equals(first, second, false, CultureInfo.InvariantCulture);
return Equals(first, second, false, CultureInfo.InvariantCulture);
}
/// <summary> /// <summary>
/// Used to compare two objects for equality converting the second to the type of the first, if required. /// Used to compare two objects for equality converting the second to the type of the first, if required.
@ -606,9 +604,7 @@ namespace System.Management.Automation
/// to specify the type of string comparison </param> /// to specify the type of string comparison </param>
/// <returns>True if first is equal to the second.</returns> /// <returns>True if first is equal to the second.</returns>
public static bool Equals(object first, object second, bool ignoreCase) public static bool Equals(object first, object second, bool ignoreCase)
{ => Equals(first, second, ignoreCase, CultureInfo.InvariantCulture);
return Equals(first, second, ignoreCase, CultureInfo.InvariantCulture);
}
/// <summary> /// <summary>
/// Used to compare two objects for equality converting the second to the type of the first, if required. /// Used to compare two objects for equality converting the second to the type of the first, if required.
@ -646,25 +642,28 @@ namespace System.Management.Automation
if (first == null) if (first == null)
{ {
if (second == null) return true; return IsNullLike(second);
return false;
} }
if (second == null) if (second == null)
{ {
return false; // first is not null return IsNullLike(first);
} }
string firstString = first as string;
string secondString; string secondString;
if (firstString != null) if (first is string firstString)
{ {
secondString = second as string ?? (string)LanguagePrimitives.ConvertTo(second, typeof(string), culture); secondString = second as string ?? (string)LanguagePrimitives.ConvertTo(second, typeof(string), culture);
return (culture.CompareInfo.Compare(firstString, secondString, return culture.CompareInfo.Compare(
ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None) == 0); firstString,
secondString,
ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None) == 0;
} }
if (first.Equals(second)) return true; if (first.Equals(second))
{
return true;
}
Type firstType = first.GetType(); Type firstType = first.GetType();
Type secondType = second.GetType(); Type secondType = second.GetType();
@ -708,24 +707,24 @@ namespace System.Management.Automation
/// Helper method for [Try]Compare to determine object ordering with null. /// Helper method for [Try]Compare to determine object ordering with null.
/// </summary> /// </summary>
/// <param name="value">The numeric value to compare to null.</param> /// <param name="value">The numeric value to compare to null.</param>
/// <param name="numberIsRightHandSide">True if the number to compare is on the right hand side if the comparison.</param> /// <param name="numberIsRightHandSide">True if the number to compare is on the right hand side in the comparison.</param>
private static int CompareObjectToNull(object value, bool numberIsRightHandSide) private static int CompareObjectToNull(object value, bool numberIsRightHandSide)
{ {
var i = numberIsRightHandSide ? -1 : 1; var i = numberIsRightHandSide ? -1 : 1;
// If it's a positive number, including 0, it's greater than null // If it's a positive number, including 0, it's greater than null
// for everything else it's less than zero... // for everything else it's less than zero...
switch (value) return value switch
{ {
case Int16 i16: return Math.Sign(i16) < 0 ? -i : i; Int16 i16 => Math.Sign(i16) < 0 ? -i : i,
case Int32 i32: return Math.Sign(i32) < 0 ? -i : i; Int32 i32 => Math.Sign(i32) < 0 ? -i : i,
case Int64 i64: return Math.Sign(i64) < 0 ? -i : i; Int64 i64 => Math.Sign(i64) < 0 ? -i : i,
case sbyte sby: return Math.Sign(sby) < 0 ? -i : i; sbyte s => Math.Sign(s) < 0 ? -i : i,
case float f: return Math.Sign(f) < 0 ? -i : i; float f => Math.Sign(f) < 0 ? -i : i,
case double d: return Math.Sign(d) < 0 ? -i : i; double d => Math.Sign(d) < 0 ? -i : i,
case decimal de: return Math.Sign(de) < 0 ? -i : i; decimal m => Math.Sign(m) < 0 ? -i : i,
default: return i; _ => IsNullLike(value) ? 0 : i
} };
} }
/// <summary> /// <summary>
@ -741,9 +740,7 @@ namespace System.Management.Automation
/// to the type of <paramref name="first"/>. /// to the type of <paramref name="first"/>.
/// </exception> /// </exception>
public static int Compare(object first, object second) public static int Compare(object first, object second)
{ => LanguagePrimitives.Compare(first, second, false, CultureInfo.InvariantCulture);
return LanguagePrimitives.Compare(first, second, false, CultureInfo.InvariantCulture);
}
/// <summary> /// <summary>
/// Compare first and second, converting second to the /// Compare first and second, converting second to the
@ -759,9 +756,7 @@ namespace System.Management.Automation
/// to the type of <paramref name="first"/>. /// to the type of <paramref name="first"/>.
/// </exception> /// </exception>
public static int Compare(object first, object second, bool ignoreCase) public static int Compare(object first, object second, bool ignoreCase)
{ => LanguagePrimitives.Compare(first, second, ignoreCase, CultureInfo.InvariantCulture);
return LanguagePrimitives.Compare(first, second, ignoreCase, CultureInfo.InvariantCulture);
}
/// <summary> /// <summary>
/// Compare first and second, converting second to the /// Compare first and second, converting second to the
@ -779,15 +774,12 @@ namespace System.Management.Automation
/// </exception> /// </exception>
public static int Compare(object first, object second, bool ignoreCase, IFormatProvider formatProvider) public static int Compare(object first, object second, bool ignoreCase, IFormatProvider formatProvider)
{ {
if (formatProvider == null) formatProvider ??= CultureInfo.InvariantCulture;
{
formatProvider = CultureInfo.InvariantCulture;
}
var culture = formatProvider as CultureInfo; var culture = formatProvider as CultureInfo;
if (culture == null) if (culture == null)
{ {
throw PSTraceSource.NewArgumentException("formatProvider"); throw PSTraceSource.NewArgumentException(nameof(formatProvider));
} }
first = PSObject.Base(first); first = PSObject.Base(first);
@ -795,7 +787,7 @@ namespace System.Management.Automation
if (first == null) if (first == null)
{ {
return second == null ? 0 : CompareObjectToNull(second, true); return CompareObjectToNull(second, true);
} }
if (second == null) if (second == null)
@ -805,7 +797,7 @@ namespace System.Management.Automation
if (first is string firstString) if (first is string firstString)
{ {
string secondString = second as string; var secondString = second as string;
if (secondString == null) if (secondString == null)
{ {
try try
@ -814,19 +806,26 @@ namespace System.Management.Automation
} }
catch (PSInvalidCastException e) catch (PSInvalidCastException e)
{ {
throw PSTraceSource.NewArgumentException("second", ExtendedTypeSystem.ComparisonFailure, throw PSTraceSource.NewArgumentException(
first.ToString(), second.ToString(), e.Message); nameof(second),
ExtendedTypeSystem.ComparisonFailure,
first.ToString(),
second.ToString(),
e.Message);
} }
} }
return culture.CompareInfo.Compare(firstString, secondString, return culture.CompareInfo.Compare(
ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); firstString,
secondString,
ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
} }
Type firstType = first.GetType(); Type firstType = first.GetType();
Type secondType = second.GetType(); Type secondType = second.GetType();
int firstIndex = LanguagePrimitives.TypeTableIndex(firstType); int firstIndex = LanguagePrimitives.TypeTableIndex(firstType);
int secondIndex = LanguagePrimitives.TypeTableIndex(secondType); int secondIndex = LanguagePrimitives.TypeTableIndex(secondType);
if ((firstIndex != -1) && (secondIndex != -1)) if ((firstIndex != -1) && (secondIndex != -1))
{ {
return LanguagePrimitives.NumericCompare(first, second, firstIndex, secondIndex); return LanguagePrimitives.NumericCompare(first, second, firstIndex, secondIndex);
@ -839,8 +838,12 @@ namespace System.Management.Automation
} }
catch (PSInvalidCastException e) catch (PSInvalidCastException e)
{ {
throw PSTraceSource.NewArgumentException("second", ExtendedTypeSystem.ComparisonFailure, throw PSTraceSource.NewArgumentException(
first.ToString(), second.ToString(), e.Message); nameof(second),
ExtendedTypeSystem.ComparisonFailure,
first.ToString(),
second.ToString(),
e.Message);
} }
if (first is IComparable firstComparable) if (first is IComparable firstComparable)
@ -855,7 +858,7 @@ namespace System.Management.Automation
// At this point, we know that they aren't equal but we have no way of // At this point, we know that they aren't equal but we have no way of
// knowing which should compare greater than the other so we throw an exception. // knowing which should compare greater than the other so we throw an exception.
throw PSTraceSource.NewArgumentException("first", ExtendedTypeSystem.NotIcomparable, first.ToString()); throw PSTraceSource.NewArgumentException(nameof(first), ExtendedTypeSystem.NotIcomparable, first.ToString());
} }
/// <summary> /// <summary>
@ -868,9 +871,7 @@ namespace System.Management.Automation
/// zero if it is greater or zero if they are the same.</param> /// zero if it is greater or zero if they are the same.</param>
/// <returns>True if the comparison was successful, false otherwise.</returns> /// <returns>True if the comparison was successful, false otherwise.</returns>
public static bool TryCompare(object first, object second, out int result) public static bool TryCompare(object first, object second, out int result)
{ => TryCompare(first, second, ignoreCase: false, CultureInfo.InvariantCulture, out result);
return TryCompare(first, second, ignoreCase: false, CultureInfo.InvariantCulture, out result);
}
/// <summary> /// <summary>
/// Tries to compare first and second, converting second to the type of the first, if necessary. /// Tries to compare first and second, converting second to the type of the first, if necessary.
@ -882,9 +883,7 @@ namespace System.Management.Automation
/// <param name="result">Less than zero if first is smaller than second, more than zero if it is greater or zero if they are the same.</param> /// <param name="result">Less than zero if first is smaller than second, more than zero if it is greater or zero if they are the same.</param>
/// <returns>True if the comparison was successful, false otherwise.</returns> /// <returns>True if the comparison was successful, false otherwise.</returns>
public static bool TryCompare(object first, object second, bool ignoreCase, out int result) public static bool TryCompare(object first, object second, bool ignoreCase, out int result)
{ => TryCompare(first, second, ignoreCase, CultureInfo.InvariantCulture, out result);
return TryCompare(first, second, ignoreCase, CultureInfo.InvariantCulture, out result);
}
/// <summary> /// <summary>
/// Tries to compare first and second, converting second to the type of the first, if necessary. /// Tries to compare first and second, converting second to the type of the first, if necessary.
@ -900,10 +899,7 @@ namespace System.Management.Automation
public static bool TryCompare(object first, object second, bool ignoreCase, IFormatProvider formatProvider, out int result) public static bool TryCompare(object first, object second, bool ignoreCase, IFormatProvider formatProvider, out int result)
{ {
result = 0; result = 0;
if (formatProvider == null) formatProvider ??= CultureInfo.InvariantCulture;
{
formatProvider = CultureInfo.InvariantCulture;
}
if (!(formatProvider is CultureInfo culture)) if (!(formatProvider is CultureInfo culture))
{ {
@ -988,8 +984,10 @@ namespace System.Management.Automation
public static bool IsTrue(object obj) public static bool IsTrue(object obj)
{ {
// null is a valid argument - it converts to false... // null is a valid argument - it converts to false...
if (obj == null || obj == AutomationNull.Value) if (IsNullLike(obj))
{
return false; return false;
}
obj = PSObject.Base(obj); obj = PSObject.Base(obj);
@ -1015,8 +1013,7 @@ namespace System.Management.Automation
if (objType == typeof(SwitchParameter)) if (objType == typeof(SwitchParameter))
return ((SwitchParameter)obj).ToBool(); return ((SwitchParameter)obj).ToBool();
IList objectArray = obj as IList; if (obj is IList objectArray)
if (objectArray != null)
{ {
return IsTrue(objectArray); return IsTrue(objectArray);
} }
@ -1062,14 +1059,19 @@ namespace System.Management.Automation
} }
/// <summary> /// <summary>
/// Internal routine that determines if an object meets any of our criteria for null. /// Internal routine that determines if an object meets any of our criteria for true null.
/// </summary> /// </summary>
/// <param name="obj">The object to test.</param> /// <param name="obj">The object to test.</param>
/// <returns>True if the object is null.</returns> /// <returns>True if the object is null.</returns>
internal static bool IsNull(object obj) public static bool IsNull(object obj) => obj == null || obj == AutomationNull.Value;
{
return (obj == null || obj == AutomationNull.Value); /// <summary>
} /// Internal routine that determines if an object meets any of our criteria for null.
/// This method additionally checks for <see cref="NullString.Value"/> and <see cref="DBNull.Value"/>
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns>True if the object is null.</returns>
public static bool IsNullLike(object obj) => obj == DBNull.Value || obj == NullString.Value || IsNull(obj);
/// <summary> /// <summary>
/// Auxiliary for the cases where we want a new PSObject or null. /// Auxiliary for the cases where we want a new PSObject or null.
@ -3100,15 +3102,17 @@ namespace System.Management.Automation
return AutomationNull.Value; return AutomationNull.Value;
} }
private static bool ConvertClassToBool(object valueToConvert, private static bool ConvertClassToBool(
Type resultType, object valueToConvert,
bool recursion, Type resultType,
PSObject originalValueToConvert, bool recursion,
IFormatProvider formatProvider, PSObject originalValueToConvert,
TypeTable backupTable) IFormatProvider formatProvider,
TypeTable backupTable)
{ {
typeConversion.WriteLine("Converting ref to boolean."); typeConversion.WriteLine("Converting ref to boolean.");
return valueToConvert != null; // Both NullString and DBNull should be treated the same as true nulls for the purposes of this conversion.
return !IsNullLike(valueToConvert);
} }
private static bool ConvertValueToBool(object valueToConvert, private static bool ConvertValueToBool(object valueToConvert,
@ -4724,10 +4728,11 @@ namespace System.Management.Automation
{ {
PSObject valueAsPsObj; PSObject valueAsPsObj;
Type originalType; Type originalType;
if (valueToConvert == null || valueToConvert == AutomationNull.Value)
if (IsNull(valueToConvert))
{ {
valueAsPsObj = null;
originalType = typeof(Null); originalType = typeof(Null);
valueAsPsObj = null;
} }
else else
{ {

View file

@ -3028,7 +3028,9 @@ namespace System.Management.Automation.Language
if (target.Value == null) if (target.Value == null)
{ {
return new DynamicMetaObject( return new DynamicMetaObject(
arg.Value == null ? ExpressionCache.BoxedTrue : ExpressionCache.BoxedFalse, LanguagePrimitives.IsNullLike(arg.Value)
? ExpressionCache.BoxedTrue
: ExpressionCache.BoxedFalse,
target.CombineRestrictions(arg)); target.CombineRestrictions(arg));
} }
@ -3036,7 +3038,9 @@ namespace System.Management.Automation.Language
if (enumerable == null && arg.Value == null) if (enumerable == null && arg.Value == null)
{ {
return new DynamicMetaObject( return new DynamicMetaObject(
ExpressionCache.BoxedFalse, LanguagePrimitives.IsNullLike(target.Value)
? ExpressionCache.BoxedTrue
: ExpressionCache.BoxedFalse,
target.CombineRestrictions(arg)); target.CombineRestrictions(arg));
} }
@ -3051,14 +3055,19 @@ namespace System.Management.Automation.Language
if (target.Value == null) if (target.Value == null)
{ {
return new DynamicMetaObject( return new DynamicMetaObject(
arg.Value == null ? ExpressionCache.BoxedFalse : ExpressionCache.BoxedTrue, LanguagePrimitives.IsNullLike(arg.Value)
? ExpressionCache.BoxedFalse
: ExpressionCache.BoxedTrue,
target.CombineRestrictions(arg)); target.CombineRestrictions(arg));
} }
var enumerable = PSEnumerableBinder.IsEnumerable(target); var enumerable = PSEnumerableBinder.IsEnumerable(target);
if (enumerable == null && arg.Value == null) if (enumerable == null && arg.Value == null)
{ {
return new DynamicMetaObject(ExpressionCache.BoxedTrue, return new DynamicMetaObject(
LanguagePrimitives.IsNullLike(target.Value)
? ExpressionCache.BoxedFalse
: ExpressionCache.BoxedTrue,
target.CombineRestrictions(arg)); target.CombineRestrictions(arg));
} }

View file

@ -0,0 +1,89 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
using namespace System.Management.Automation.Internal
Describe 'Null Representatives' -Tags 'CI' {
Context 'Comparisons with $null' {
BeforeAll {
$TestValues = @(
@{ Value = { [AutomationNull]::Value } }
@{ Value = { [DBNull]::Value } }
@{ Value = { [NullString]::Value } }
)
}
It '<Value> should be equivalent to $null (RHS: $null)' -TestCases $TestValues {
param($Value)
$Value.InvokeReturnAsIs() -eq $null | Should -BeTrue
}
It '$null should be equivalent to <Value> (LHS: $null)' -TestCases $TestValues {
param($Value)
$null -eq $Value.InvokeReturnAsIs() | Should -BeTrue
}
}
Context 'Comparisons with other null representatives' {
<#
The only unequal null-representatives are NullString and DBNull.
AutomationNull and $null are always considered equal already, so therefore NullString compares as
true with both of them, as does DBNull.
However, as NullString and DBNull have different purposes, it makes more sense to consider them unequal
when directly compared with each other.
#>
It 'DBNull should not be equal to NullString' {
[DBNull]::Value -eq [NullString]::Value | Should -BeFalse
[NullString]::Value -eq [DBNull]::Value | Should -BeFalse
}
}
Context 'Casting Behaviour' {
BeforeAll {
$TestValues = @(
@{ Value = { $null } }
@{ Value = { [DBNull]::Value } }
@{ Value = { [NullString]::Value } }
@{ Value = { [AutomationNull]::Value } }
)
}
It '<Value> should cast to $false' -TestCases $TestValues {
param($Value)
[bool]($Value.InvokeReturnAsIs()) | Should -BeFalse
}
It '-not <Value> should be $true' -TestCases $TestValues {
param($Value)
-not $Value.InvokeReturnAsIs() | Should -BeTrue
}
It '<Value> should be treated as $false by Where-Object' -TestCases $TestValues {
param($Value)
100 | Where-Object { $Value.InvokeReturnAsIs() } | Should -BeNullOrEmpty
}
}
Context 'Collection Comparisons' {
BeforeAll {
$NullArray = $null, $null, [DBNull]::Value, $null, $null, [NullString]::Value
}
It '<Value> should correctly filter the array and return <ExpectedCount> results' {
param($Value, $ExpectedCount)
$NullArray -eq $Value | Should -HaveCount $ExpectedCount
} -TestCases @(
@{ Value = $null; ExpectedCount = 6 }
@{ Value = [DBNull]::Value; ExpectedCount = 5 }
@{ Value = [NullString]::Value; ExpectedCount = 5 }
)
}
}

View file

@ -109,8 +109,8 @@ Describe "SemanticVersion api tests" -Tags 'CI' {
@{ lhs = $v1_0_0_alpha; rhs = $v1_0_0_alpha2 } @{ lhs = $v1_0_0_alpha; rhs = $v1_0_0_alpha2 }
@{ lhs = $v1_0_0_alpha; rhs = $v1_0_0 } @{ lhs = $v1_0_0_alpha; rhs = $v1_0_0 }
@{ lhs = $v1_0_0_beta; rhs = $v1_0_0 } @{ lhs = $v1_0_0_beta; rhs = $v1_0_0 }
@{ lhs = $v2_1_0; rhs = "3.0"} @{ lhs = $v2_1_0; rhs = "3.0" }
@{ lhs = "1.5"; rhs = $v2_1_0} @{ lhs = "1.5"; rhs = $v2_1_0 }
) )
} }
@ -176,38 +176,39 @@ Describe "SemanticVersion api tests" -Tags 'CI' {
Context "Error handling" { Context "Error handling" {
It "<name>: '<version>'" -TestCases @( It "<name>: '<version>'" -TestCases @(
@{ name = "Missing parts: 'null'"; errorId = "PSArgumentNullException";expectedResult = $false; version = $null } @{ name = "Missing parts: 'null'"; errorId = "PSArgumentNullException"; expectedResult = $false; version = $null }
@{ name = "Missing parts: 'NullString'"; errorId = "PSArgumentNullException";expectedResult = $false; version = [NullString]::Value } @{ name = "Missing parts: 'NullString'"; errorId = "PSArgumentNullException"; expectedResult = $false; version = [NullString]::Value }
@{ name = "Missing parts: 'EmptyString'";errorId = "FormatException"; expectedResult = $false; version = "" } @{ name = "Missing parts: 'EmptyString'"; errorId = "FormatException"; expectedResult = $false; version = "" }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "-" } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "-" }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "." } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "." }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "+" } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "+" }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "-alpha" } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "-alpha" }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1..0" } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1..0" }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.-alpha" } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.-alpha" }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.+alpha" } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.+alpha" }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.0-alpha+" } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.0-alpha+" }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.0-+" } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.0-+" }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.0+-" } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.0+-" }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.0+" } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.0+" }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.0-" } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.0-" }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.0." } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.0." }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0." } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0." }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.." } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = "1.0.." }
@{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = ".0.0" } @{ name = "Missing parts"; errorId = "FormatException"; expectedResult = $false; version = ".0.0" }
@{ name = "Range check of versions"; errorId = "FormatException"; expectedResult = $false; version = "-1.0.0" } @{ name = "Range check of versions"; errorId = "FormatException"; expectedResult = $false; version = "-1.0.0" }
@{ name = "Range check of versions"; errorId = "FormatException"; expectedResult = $false; version = "1.-1.0" } @{ name = "Range check of versions"; errorId = "FormatException"; expectedResult = $false; version = "1.-1.0" }
@{ name = "Range check of versions"; errorId = "FormatException"; expectedResult = $false; version = "1.0.-1" } @{ name = "Range check of versions"; errorId = "FormatException"; expectedResult = $false; version = "1.0.-1" }
@{ name = "Format errors"; errorId = "FormatException"; expectedResult = $false; version = "aa.0.0" } @{ name = "Format errors"; errorId = "FormatException"; expectedResult = $false; version = "aa.0.0" }
@{ name = "Format errors"; errorId = "FormatException"; expectedResult = $false; version = "1.bb.0" } @{ name = "Format errors"; errorId = "FormatException"; expectedResult = $false; version = "1.bb.0" }
@{ name = "Format errors"; errorId = "FormatException"; expectedResult = $false; version = "1.0.cc" } @{ name = "Format errors"; errorId = "FormatException"; expectedResult = $false; version = "1.0.cc" }
) { ) {
param($version, $expectedResult, $errorId) param($version, $expectedResult, $errorId)
{ [SemanticVersion]::new($version) } | Should -Throw -ErrorId $errorId { [SemanticVersion]::new($version) } | Should -Throw -ErrorId $errorId
if ($version -eq $null) { if ([LanguagePrimitives]::IsNull($version)) {
# PowerShell convert $null to Empty string # PowerShell convert $null to Empty string
{ [SemanticVersion]::Parse($version) } | Should -Throw -ErrorId "FormatException" { [SemanticVersion]::Parse($version) } | Should -Throw -ErrorId "FormatException"
} else { }
else {
{ [SemanticVersion]::Parse($version) } | Should -Throw -ErrorId $errorId { [SemanticVersion]::Parse($version) } | Should -Throw -ErrorId $errorId
} }
$semVer = $null $semVer = $null