From 39321c933e0312c5077178cbce1549285ba2bab4 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Wed, 13 Nov 2019 10:10:41 -0600 Subject: [PATCH 01/10] Parameterize targetFramework in deploy script --- scripts/Deploy-MSBuild.ps1 | 64 +++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/scripts/Deploy-MSBuild.ps1 b/scripts/Deploy-MSBuild.ps1 index 0939abb2d56..b39e3bc5935 100644 --- a/scripts/Deploy-MSBuild.ps1 +++ b/scripts/Deploy-MSBuild.ps1 @@ -28,39 +28,45 @@ $BackupFolder = New-Item (Join-Path $destination -ChildPath "Backup-$(Get-Date - Write-Verbose "Copying $configuration MSBuild to $destination" Write-Host "Existing MSBuild assemblies backed up to $BackupFolder" +if ($runtime -eq "Desktop") { + $targetFramework = "net472" +} else { + $targetFramework = "netcoreapp2.1" +} + $filesToCopyToBin = @( - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\MSBuild.exe" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.Build.dll" - "artifacts\bin\Microsoft.Build.Conversion\$configuration\net472\Microsoft.Build.Conversion.Core.dll" - "artifacts\bin\Microsoft.Build.Engine\$configuration\net472\Microsoft.Build.Engine.dll" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.Build.Framework.dll" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.Build.Tasks.Core.dll" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.Build.Utilities.Core.dll" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\MSBuild.exe" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Build.dll" + "artifacts\bin\Microsoft.Build.Conversion\$configuration\$targetFramework\Microsoft.Build.Conversion.Core.dll" + "artifacts\bin\Microsoft.Build.Engine\$configuration\$targetFramework\Microsoft.Build.Engine.dll" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Build.Framework.dll" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Build.Tasks.Core.dll" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Build.Utilities.Core.dll" "artifacts\bin\MSBuildTaskHost\$configuration\net35\MSBuildTaskHost.exe" "artifacts\bin\MSBuildTaskHost\$configuration\net35\MSBuildTaskHost.pdb" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.Common.CrossTargeting.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.Common.CurrentVersion.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.Common.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.CSharp.CrossTargeting.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.CSharp.CurrentVersion.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.CSharp.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.Data.Entity.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.Managed.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.Net.props" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.NetFramework.CurrentVersion.props" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.NetFramework.CurrentVersion.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.NetFramework.props" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.NetFramework.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.ServiceModel.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.VisualBasic.CrossTargeting.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.VisualBasic.CurrentVersion.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.VisualBasic.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.WinFx.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.WorkflowBuildExtensions.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Microsoft.Xaml.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Workflow.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\net472\Workflow.VisualBasic.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Common.CrossTargeting.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Common.CurrentVersion.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Common.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.CSharp.CrossTargeting.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.CSharp.CurrentVersion.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.CSharp.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Data.Entity.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Managed.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Net.props" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.NetFramework.CurrentVersion.props" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.NetFramework.CurrentVersion.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.NetFramework.props" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.NetFramework.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.ServiceModel.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.VisualBasic.CrossTargeting.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.VisualBasic.CurrentVersion.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.VisualBasic.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.WinFx.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.WorkflowBuildExtensions.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Xaml.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Workflow.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Workflow.VisualBasic.targets" ) foreach ($file in $filesToCopyToBin) { From 0070f0791f042800ed6dabab20ff06554589e606 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Wed, 13 Nov 2019 10:36:40 -0600 Subject: [PATCH 02/10] Copy files according to runtime --- scripts/Deploy-MSBuild.ps1 | 41 ++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/scripts/Deploy-MSBuild.ps1 b/scripts/Deploy-MSBuild.ps1 index b39e3bc5935..9da67ecc15a 100644 --- a/scripts/Deploy-MSBuild.ps1 +++ b/scripts/Deploy-MSBuild.ps1 @@ -3,7 +3,9 @@ Param( [Parameter(Mandatory = $true)] [string] $destination, [ValidateSet('Debug','Release')] - [string] $configuration = "Debug" + [string] $configuration = "Debug", + [ValidateSet('Core','Desktop')] + [string] $runtime = "Desktop" ) function Copy-WithBackup ($origin) { @@ -35,15 +37,10 @@ if ($runtime -eq "Desktop") { } $filesToCopyToBin = @( - "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\MSBuild.exe" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Build.dll" - "artifacts\bin\Microsoft.Build.Conversion\$configuration\$targetFramework\Microsoft.Build.Conversion.Core.dll" - "artifacts\bin\Microsoft.Build.Engine\$configuration\$targetFramework\Microsoft.Build.Engine.dll" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Build.Framework.dll" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Build.Tasks.Core.dll" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Build.Utilities.Core.dll" - "artifacts\bin\MSBuildTaskHost\$configuration\net35\MSBuildTaskHost.exe" - "artifacts\bin\MSBuildTaskHost\$configuration\net35\MSBuildTaskHost.pdb" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Common.CrossTargeting.targets" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Common.CurrentVersion.targets" @@ -51,24 +48,42 @@ $filesToCopyToBin = @( "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.CSharp.CrossTargeting.targets" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.CSharp.CurrentVersion.targets" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.CSharp.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Data.Entity.targets" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Managed.targets" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Net.props" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.NetFramework.CurrentVersion.props" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.NetFramework.CurrentVersion.targets" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.NetFramework.props" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.NetFramework.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.ServiceModel.targets" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.VisualBasic.CrossTargeting.targets" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.VisualBasic.CurrentVersion.targets" "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.VisualBasic.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.WinFx.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.WorkflowBuildExtensions.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Xaml.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Workflow.targets" - "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Workflow.VisualBasic.targets" ) +if ($runtime -eq "Desktop") { + $runtimeSpecificFiles = @( + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\MSBuild.exe" + "artifacts\bin\Microsoft.Build.Conversion\$configuration\$targetFramework\Microsoft.Build.Conversion.Core.dll" + "artifacts\bin\Microsoft.Build.Engine\$configuration\$targetFramework\Microsoft.Build.Engine.dll" + + "artifacts\bin\MSBuildTaskHost\$configuration\net35\MSBuildTaskHost.exe" + "artifacts\bin\MSBuildTaskHost\$configuration\net35\MSBuildTaskHost.pdb" + + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Data.Entity.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.ServiceModel.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.WinFx.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.WorkflowBuildExtensions.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Microsoft.Xaml.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Workflow.targets" + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\Workflow.VisualBasic.targets" + ) +} else { + $runtimeSpecificFiles = @( + "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework\MSBuild.dll" + ) +} + +$filesToCopyToBin += $runtimeSpecificFiles + foreach ($file in $filesToCopyToBin) { Copy-WithBackup $([IO.Path]::Combine($PSScriptRoot, "..", $file)) } From e5f47a3f072025e4ac487bce03f64892e5d9c21b Mon Sep 17 00:00:00 2001 From: Nick Guerrera Date: Thu, 14 Nov 2019 09:23:29 -0800 Subject: [PATCH 03/10] Add simple replacement for System.Version Will be used to implement version comparison intrinic property functions. Allows major version only (e.g. "3" is 3.0.0.0), ignores leading 'v' (e.g. "v3.0" is 3.0.0.0). Ignores semver prerelease and metadata portions (e.g. "1.0.0-preview+info" is 1.0.0.0). Treats unspecified components as 0 (e.g. x == x.0 == x.0.0 == x.0.0.0). Unlike System.Version, does not tolerate whitespace, and '+' is ignored as semver metadata as described above, not tolerated as positive sign of integer component. Tolerating leading 'v' allows using $(TargetFrameworkVersion) directly. Ignoring semver portions allows, for example, checking >= major.minor while still in development of that release. Implemented as a struct to avoid heap allocation. Parsing is done without heap allocation at all on .NET Core. However, on .NET Framework, the integer component substrings are allocated as there is no int.Parse on span there. --- .../Evaluation/SimpleVersion_Tests.cs | 309 ++++++++++++++++++ src/Build/Microsoft.Build.csproj | 1 + src/Build/Resources/Strings.resx | 3 + src/Build/Utilities/SimpleVersion.cs | 188 +++++++++++ 4 files changed, 501 insertions(+) create mode 100644 src/Build.UnitTests/Evaluation/SimpleVersion_Tests.cs create mode 100644 src/Build/Utilities/SimpleVersion.cs diff --git a/src/Build.UnitTests/Evaluation/SimpleVersion_Tests.cs b/src/Build.UnitTests/Evaluation/SimpleVersion_Tests.cs new file mode 100644 index 00000000000..7c08340bcf3 --- /dev/null +++ b/src/Build.UnitTests/Evaluation/SimpleVersion_Tests.cs @@ -0,0 +1,309 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Utilities; +using System; +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.Build.UnitTests.Evaluation +{ + public class SimpleVersion_Tests + { + [Fact] + public void Ctor_Default() + { + VerifyVersion(new SimpleVersion(), 0, 0, 0, 0); + } + + [Theory] + [InlineData(0)] + [InlineData(2)] + [InlineData(int.MaxValue)] + public static void Ctor_Int(int major) + { + VerifyVersion(new SimpleVersion(major), major, 0, 0, 0); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(2, 3)] + [InlineData(int.MaxValue, int.MaxValue)] + public static void Ctor_Int_Int(int major, int minor) + { + VerifyVersion(new SimpleVersion(major, minor), major, minor, 0, 0); + } + + [Theory] + [InlineData(0, 0, 0)] + [InlineData(2, 3, 4)] + [InlineData(int.MaxValue, int.MaxValue, int.MaxValue)] + public static void Ctor_Int_Int_Int(int major, int minor, int build) + { + VerifyVersion(new SimpleVersion(major, minor, build), major, minor, build, 0); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(2, 3, 4, 7)] + [InlineData(2, 3, 4, 32767)] + [InlineData(2, 3, 4, 32768)] + [InlineData(2, 3, 4, 65535)] + [InlineData(2, 3, 4, 65536)] + [InlineData(2, 3, 4, 2147483647)] + [InlineData(2, 3, 4, 2147450879)] + [InlineData(2, 3, 4, 2147418112)] + [InlineData(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue)] + public static void Ctor_Int_Int_Int_Int(int major, int minor, int build, int revision) + { + VerifyVersion(new SimpleVersion(major, minor, build, revision), major, minor, build, revision); + } + + [Fact] + public void Ctor_NegativeMajor_ThrowsArgumentOutOfRangeException() + { + Assert.Throws("major", () => new SimpleVersion(-1, 0)); + Assert.Throws("major", () => new SimpleVersion(-1, 0, 0)); + Assert.Throws("major", () => new SimpleVersion(-1, 0, 0, 0)); + } + + [Fact] + public void Ctor_NegativeMinor_ThrowsArgumentOutOfRangeException() + { + Assert.Throws("minor", () => new SimpleVersion(0, -1)); + Assert.Throws("minor", () => new SimpleVersion(0, -1, 0)); + Assert.Throws("minor", () => new SimpleVersion(0, -1, 0, 0)); + } + + [Fact] + public void Ctor_NegativeBuild_ThrowsArgumentOutOfRangeException() + { + Assert.Throws("build", () => new SimpleVersion(0, 0, -1)); + Assert.Throws("build", () => new SimpleVersion(0, 0, -1, 0)); + } + + [Fact] + public void Ctor_NegativeRevision_ThrowsArgumentOutOfRangeException() + { + Assert.Throws("revision", () => new SimpleVersion(0, 0, 0, -1)); + } + + public static IEnumerable Comparison_TestData() + { + foreach (var input in new (SimpleVersion v1, SimpleVersion v2, int expectedSign)[] + { + (new SimpleVersion(1, 2), new SimpleVersion(1, 2), 0), + (new SimpleVersion(1, 2), new SimpleVersion(1, 3), -1), + (new SimpleVersion(1, 2), new SimpleVersion(1, 1), 1), + (new SimpleVersion(1, 2), new SimpleVersion(2, 0), -1), + (new SimpleVersion(1, 2), new SimpleVersion(1, 2, 1), -1), + (new SimpleVersion(1, 2), new SimpleVersion(1, 2, 0, 1), -1), + (new SimpleVersion(1, 2), new SimpleVersion(1, 0), 1), + (new SimpleVersion(1, 2), new SimpleVersion(1, 0, 1), 1), + (new SimpleVersion(1, 2), new SimpleVersion(1, 0, 0, 1), 1), + + (new SimpleVersion(3, 2, 1), new SimpleVersion(2, 2, 1), 1), + (new SimpleVersion(3, 2, 1), new SimpleVersion(3, 1, 1), 1), + (new SimpleVersion(3, 2, 1), new SimpleVersion(3, 2, 0), 1), + + (new SimpleVersion(1, 2, 3, 4), new SimpleVersion(1, 2, 3, 4), 0), + (new SimpleVersion(1, 2, 3, 4), new SimpleVersion(1, 2, 3, 5), -1), + (new SimpleVersion(1, 2, 3, 4), new SimpleVersion(1, 2, 3, 3), 1) + }) + { + yield return new object[] { input.v1, input.v2, input.expectedSign }; + yield return new object[] { input.v2, input.v1, input.expectedSign * -1 }; + } + } + + [Theory] + [MemberData(nameof(Comparison_TestData))] + public void CompareTo_ReturnsExpected(object version1Object, object version2Object, int expectedSign) + { + var version1 = (SimpleVersion)version1Object; + var version2 = (SimpleVersion)version2Object; + + Assert.Equal(expectedSign, Comparer.Default.Compare(version1, version2)); + Assert.Equal(expectedSign, Math.Sign(version1.CompareTo(version2))); + } + + [Theory] + [MemberData(nameof(Comparison_TestData))] + public void ComparisonOperators_ReturnExpected(object version1Object, object version2Object, int expectedSign) + { + var version1 = (SimpleVersion)version1Object; + var version2 = (SimpleVersion)version2Object; + + if (expectedSign < 0) + { + Assert.True(version1 < version2); + Assert.True(version1 <= version2); + Assert.False(version1 == version2); + Assert.False(version1 >= version2); + Assert.False(version1 > version2); + Assert.True(version1 != version2); + } + else if (expectedSign == 0) + { + Assert.False(version1 < version2); + Assert.True(version1 <= version2); + Assert.True(version1 == version2); + Assert.True(version1 >= version2); + Assert.False(version1 > version2); + Assert.False(version1 != version2); + } + else + { + Assert.False(version1 < version2); + Assert.False(version1 <= version2); + Assert.False(version1 == version2); + Assert.True(version1 >= version2); + Assert.True(version1 > version2); + Assert.True(version1 != version2); + } + } + + public static IEnumerable Equals_TestData() + { + yield return new object[] { new SimpleVersion(2, 3), new SimpleVersion(2, 3), true }; + yield return new object[] { new SimpleVersion(2, 3), new SimpleVersion(2, 4), false }; + yield return new object[] { new SimpleVersion(2, 3), new SimpleVersion(3, 3), false }; + + yield return new object[] { new SimpleVersion(2, 3, 4), new SimpleVersion(2, 3, 4), true }; + yield return new object[] { new SimpleVersion(2, 3, 4), new SimpleVersion(2, 3, 5), false }; + yield return new object[] { new SimpleVersion(2, 3, 4), new SimpleVersion(2, 3), false }; + + yield return new object[] { new SimpleVersion(2, 3, 4, 5), new SimpleVersion(2, 3, 4, 5), true }; + yield return new object[] { new SimpleVersion(2, 3, 4, 5), new SimpleVersion(2, 3, 4, 6), false }; + yield return new object[] { new SimpleVersion(2, 3, 4, 5), new SimpleVersion(2, 3), false }; + yield return new object[] { new SimpleVersion(2, 3, 4, 5), new SimpleVersion(2, 3, 4), false }; + + yield return new object[] { new SimpleVersion(2, 3, 0), new SimpleVersion(2, 3), true }; + yield return new object[] { new SimpleVersion(2, 3, 4, 0), new SimpleVersion(2, 3, 4), true }; + + yield return new object[] { new SimpleVersion(2, 3, 4, 5), new TimeSpan(), false }; + yield return new object[] { new SimpleVersion(2, 3, 4, 5), null, false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public static void Equals_Other_ReturnsExpected(object version1Object, object version2Object, bool expected) + { + var version1 = (SimpleVersion)version1Object; + + if (version2Object is SimpleVersion version2) + { + Assert.Equal(expected, version1.Equals(version2)); + Assert.Equal(expected, version1 == version2); + Assert.Equal(!expected, version1 != version2); + } + + Assert.Equal(expected, version1.Equals(version2Object)); + + if (version2Object != null) + { + Assert.Equal(expected, version1Object.GetHashCode() == version2Object.GetHashCode()); + } + } + + public static IEnumerable Parse_Valid_TestData() + { + foreach (var prefix in new[] { "", "v", "V"}) + { + foreach (var suffix in new[] { "", "-pre", "-pre+metadata", "+metadata"}) + { + yield return new object[] { $"{prefix}1{suffix}", new SimpleVersion(1) }; + yield return new object[] { $"{prefix}1.2{suffix}", new SimpleVersion(1, 2) }; + yield return new object[] { $"{prefix}1.2.3{suffix}", new SimpleVersion(1, 2, 3) }; + yield return new object[] { $"{prefix}1.2.3.4{suffix}", new SimpleVersion(1, 2, 3, 4) }; + yield return new object[] { $"{prefix}2147483647.2147483647.2147483647.2147483647{suffix}", new SimpleVersion(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue) }; + } + } + } + + [Theory] + [MemberData(nameof(Parse_Valid_TestData))] + public static void Parse_ValidInput_ReturnsExpected(string input, object expected) + { + Assert.Equal(expected, SimpleVersion.Parse(input)); + } + + public static IEnumerable Parse_Invalid_TestData() + { + yield return new object[] { null, typeof(ArgumentNullException) }; // Input is null + + yield return new object[] { "", typeof(FormatException) }; // Input is empty + yield return new object[] { "1,2,3,4", typeof(FormatException) }; // Input contains invalid separator + yield return new object[] { "1.2.3.4.5", typeof(FormatException) }; // Input has more than 4 version components + + yield return new object[] { "1." , typeof(FormatException) }; // Input contains empty component + yield return new object[] { "1.2,", typeof(FormatException) }; // Input contains empty component + yield return new object[] { "1.2.3.", typeof(FormatException) }; // Input contains empty component + yield return new object[] { "1.2.3.4.", typeof(FormatException) }; // Input contains empty component + + yield return new object[] { "NotAVersion", typeof(FormatException) }; // Input contains non-numeric value + yield return new object[] { "b.2.3.4", typeof(FormatException) }; // Input contains non-numeric value + yield return new object[] { "1.b.3.4", typeof(FormatException) }; // Input contains non-numeric value + yield return new object[] { "1.2.b.4", typeof(FormatException) }; // Input contains non-numeric value + yield return new object[] { "1.2.3.b", typeof(FormatException) }; // Input contains non-numeric value + + yield return new object[] { "2147483648.2.3.4", typeof(FormatException) }; // Input contains a value > int.MaxValue + yield return new object[] { "1.2147483648.3.4", typeof(FormatException) }; // Input contains a value > int.MaxValue + yield return new object[] { "1.2.2147483648.4", typeof(FormatException) }; // Input contains a value > int.MaxValue + yield return new object[] { "1.2.3.2147483648", typeof(FormatException) }; // Input contains a value > int.MaxValue + + // System.Version allows whitespace around components, but we don't + yield return new object[] { "2 .3. 4. \t\r\n15 ", typeof(FormatException) }; + yield return new object[] { " 2 .3. 4. \t\r\n15 ", typeof(FormatException) }; + + // System.Version rejects these because they have negative values, but SimpleVersion strips interprest as semver prerelease to strip + // They are still invalid because the stripping leaves a empty components behind which are also not allowed as above. + yield return new object[] { "-1.2.3.4", typeof(FormatException) }; + yield return new object[] { "1.-2.3.4", typeof(FormatException) }; + yield return new object[] { "1.2.-3.4", typeof(FormatException) }; + yield return new object[] { "1.2.3.-4", typeof(FormatException) }; + + // System.Version treats this as 1.2.3.4, but SimpleVersion interprets as semver metadata to ignore yielding invalid empty string + // System.Version parses allowing leading sign, but we treat both sign indicators as beginning of semver part to strip. + yield return new object[] { "+1.+2.+3.+4", typeof(FormatException) }; + + // Only one 'v' allowed as prefix + yield return new object[] { "vv1.2.3.4", typeof(FormatException) }; + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_InvalidInput_ThrowsException(string input, Type exceptionType) + { + Assert.Throws(exceptionType, () => SimpleVersion.Parse(input)); + } + + public static IEnumerable ToString_TestData() + { + yield return new object[] { new SimpleVersion(1), "1.0.0.0"}; + yield return new object[] { new SimpleVersion(1, 2), "1.2.0.0" }; + yield return new object[] { new SimpleVersion(1, 2, 3), "1.2.3.0" }; + yield return new object[] { new SimpleVersion(1, 2, 3, 4), "1.2.3.4" }; + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public static void ToString_Invoke_ReturnsExpected(object versionObject, string expected) + { + var version = (SimpleVersion)versionObject; + + Assert.Equal(expected, version.ToString()); + } + + private static void VerifyVersion(SimpleVersion version, int major, int minor, int build, int revision) + { + Assert.Equal(major, version.Major); + Assert.Equal(minor, version.Minor); + Assert.Equal(build, version.Build); + Assert.Equal(revision, version.Revision); + } + } +} diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index c8a2253f988..5b96af2673d 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -647,6 +647,7 @@ true + true diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index c554b6dd3e8..a931f0f57a6 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -1801,6 +1801,9 @@ Utilization: {0} Average Utilization: {1:###.0} LOCALIZATION: {0} is an enum value of LoggerVerbosity. + + Version string was not in a correct format. +