diff --git a/test/Nerdbank.GitVersioning.Tests/BuildIntegrationInProjectManagedTests.cs b/test/Nerdbank.GitVersioning.Tests/BuildIntegrationInProjectManagedTests.cs new file mode 100644 index 00000000..ea0e4a47 --- /dev/null +++ b/test/Nerdbank.GitVersioning.Tests/BuildIntegrationInProjectManagedTests.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Xunit; +using Xunit.Abstractions; + +[Trait("Engine", EngineString)] +[Collection("Build")] // msbuild sets current directory in the process, so we can't have it be concurrent with other build tests. +public class BuildIntegrationInProjectManagedTests : BuildIntegrationManagedTests +{ + public BuildIntegrationInProjectManagedTests(ITestOutputHelper logger) + : base(logger) + { + } + + /// + protected override void ApplyGlobalProperties(IDictionary globalProperties) + { + base.ApplyGlobalProperties(globalProperties); + globalProperties["NBGV_CacheMode"] = "None"; + } +} diff --git a/test/Nerdbank.GitVersioning.Tests/BuildIntegrationManagedTests.cs b/test/Nerdbank.GitVersioning.Tests/BuildIntegrationManagedTests.cs index 5231dfa1..f7b839dc 100644 --- a/test/Nerdbank.GitVersioning.Tests/BuildIntegrationManagedTests.cs +++ b/test/Nerdbank.GitVersioning.Tests/BuildIntegrationManagedTests.cs @@ -9,7 +9,7 @@ [Collection("Build")] // msbuild sets current directory in the process, so we can't have it be concurrent with other build tests. public class BuildIntegrationManagedTests : SomeGitBuildIntegrationTests { - private const string EngineString = "Managed"; + protected const string EngineString = "Managed"; public BuildIntegrationManagedTests(ITestOutputHelper logger) : base(logger) diff --git a/test/Nerdbank.GitVersioning.Tests/BuildIntegrationTests.cs b/test/Nerdbank.GitVersioning.Tests/BuildIntegrationTests.cs index 6453aa7d..c694b991 100644 --- a/test/Nerdbank.GitVersioning.Tests/BuildIntegrationTests.cs +++ b/test/Nerdbank.GitVersioning.Tests/BuildIntegrationTests.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation and Contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Collections.Immutable; +using System.Globalization; using System.Reflection; using System.Text; using System.Xml; @@ -15,11 +17,20 @@ using Validation; using Xunit; using Xunit.Abstractions; +using Version = System.Version; public abstract class BuildIntegrationTests : RepoTestBase, IClassFixture { - protected const string GitVersioningTargetsFileName = "NerdBank.GitVersioning.targets"; + protected const string GitVersioningPropsFileName = "Nerdbank.GitVersioning.props"; + protected const string GitVersioningTargetsFileName = "Nerdbank.GitVersioning.targets"; protected const string UnitTestCloudBuildPrefix = "UnitTest: "; + protected static readonly string[] ToxicEnvironmentVariablePrefixes = new string[] + { + "APPVEYOR", + "SYSTEM_", + "BUILD_", + "NBGV_GitEngine", + }; protected BuildManager buildManager; protected ProjectCollection projectCollection; @@ -34,14 +45,6 @@ public abstract class BuildIntegrationTests : RepoTestBase, IClassFixture this.Context.GitCommitId?.Substring(0, VersionOptions.DefaultGitCommitIdShortFixedLength); + public static IEnumerable CloudBuildOfBranch(string branchName) + { + return new object[][] + { + new object[] { CloudBuild.AppVeyor.SetItem("APPVEYOR_REPO_BRANCH", branchName) }, + new object[] { CloudBuild.VSTS.SetItem("BUILD_SOURCEBRANCH", $"refs/heads/{branchName}") }, + new object[] { CloudBuild.VSTS.SetItem("BUILD_SOURCEBRANCH", $"refs/tags/{branchName}") }, + new object[] { CloudBuild.Teamcity.SetItem("BUILD_GIT_BRANCH", $"refs/heads/{branchName}") }, + new object[] { CloudBuild.Teamcity.SetItem("BUILD_GIT_BRANCH", $"refs/tags/{branchName}") }, + }; + } + + [Fact] + public async Task GetBuildVersion_Returns_BuildVersion_Property() + { + this.WriteVersionFile(); + this.InitializeSourceControl(); + BuildResults buildResult = await this.BuildAsync(); + Assert.Equal( + buildResult.BuildVersion, + buildResult.BuildResult.ResultsByTarget[Targets.GetBuildVersion].Items.Single().ItemSpec); + } + [Fact] public async Task GetBuildVersion_Without_Git() { @@ -79,54 +147,88 @@ public async Task GetBuildVersion_Without_Git_HighPrecisionAssemblyVersion() Assert.Equal("3.4.0", buildResult.AssemblyInformationalVersion); } - [Fact] - public async Task GetBuildVersion_Returns_BuildVersion_Property() + // TODO: add key container test. + [Theory] + [InlineData("keypair.snk", false)] + [InlineData("public.snk", true)] + [InlineData("protectedPair.pfx", true)] + public async Task AssemblyInfo_HasKeyData(string keyFile, bool delaySigned) { + TestUtilities.ExtractEmbeddedResource($@"Keys\{keyFile}", Path.Combine(this.projectDirectory, keyFile)); + this.testProject.AddProperty("SignAssembly", "true"); + this.testProject.AddProperty("AssemblyOriginatorKeyFile", keyFile); + this.testProject.AddProperty("DelaySign", delaySigned.ToString()); + this.WriteVersionFile(); - this.InitializeSourceControl(); - BuildResults buildResult = await this.BuildAsync(); - Assert.Equal( - buildResult.BuildVersion, - buildResult.BuildResult.ResultsByTarget[Targets.GetBuildVersion].Items.Single().ItemSpec); + BuildResults result = await this.BuildAsync(Targets.GenerateAssemblyNBGVVersionInfo, logVerbosity: LoggerVerbosity.Minimal); + string versionCsContent = File.ReadAllText( + Path.GetFullPath( + Path.Combine( + this.projectDirectory, + result.BuildResult.ProjectStateAfterBuild.GetPropertyValue("VersionSourceFile")))); + this.Logger.WriteLine(versionCsContent); + + SyntaxTree sourceFile = CSharpSyntaxTree.ParseText(versionCsContent); + SyntaxNode syntaxTree = await sourceFile.GetRootAsync(); + IEnumerable fields = syntaxTree.DescendantNodes().OfType(); + + var publicKeyField = (LiteralExpressionSyntax)fields.SingleOrDefault(f => f.Identifier.ValueText == "PublicKey")?.Initializer.Value; + var publicKeyTokenField = (LiteralExpressionSyntax)fields.SingleOrDefault(f => f.Identifier.ValueText == "PublicKeyToken")?.Initializer.Value; + if (Path.GetExtension(keyFile) == ".pfx") + { + // No support for PFX (yet anyway), since they're encrypted. + // Note for future: I think by this point, the user has typically already decrypted + // the PFX and stored the key pair in a key container. If we knew how to find which one, + // we could perhaps divert to that. + Assert.Null(publicKeyField); + Assert.Null(publicKeyTokenField); + } + else + { + Assert.Equal( + "002400000480000094000000060200000024000052534131000400000100010067cea773679e0ecc114b7e1d442466a90bf77c755811a0d3962a546ed716525b6508abf9f78df132ffd3fb75fe604b3961e39c52d5dfc0e6c1fb233cb4fb56b1a9e3141513b23bea2cd156cb2ef7744e59ba6b663d1f5b2f9449550352248068e85b61c68681a6103cad91b3bf7a4b50d2fabf97e1d97ac34db65b25b58cd0dc", + publicKeyField?.Token.ValueText); + Assert.Equal("ca2d1515679318f5", publicKeyTokenField?.Token.ValueText); + } } /// /// Emulate a project with an unsupported language, and verify that - /// no errors are emitted because the target is skipped. + /// one warning is emitted because the assembly info file couldn't be generated. /// [Fact] - public async Task AssemblyInfo_Suppressed() + public async Task AssemblyInfo_NotProducedWithoutCodeDomProvider() { ProjectPropertyGroupElement propertyGroup = this.testProject.CreatePropertyGroupElement(); this.testProject.AppendChild(propertyGroup); propertyGroup.AddProperty("Language", "NoCodeDOMProviderForThisLanguage"); - propertyGroup.AddProperty(Targets.GenerateAssemblyVersionInfo, "false"); this.WriteVersionFile(); - BuildResults result = await this.BuildAsync(Targets.GenerateAssemblyVersionInfo, logVerbosity: LoggerVerbosity.Minimal); + BuildResults result = await this.BuildAsync(Targets.GenerateAssemblyNBGVVersionInfo, logVerbosity: LoggerVerbosity.Minimal, assertSuccessfulBuild: false); + Assert.Equal(BuildResultCode.Failure, result.BuildResult.OverallResult); string versionCsFilePath = Path.Combine(this.projectDirectory, result.BuildResult.ProjectStateAfterBuild.GetPropertyValue("VersionSourceFile")); Assert.False(File.Exists(versionCsFilePath)); - Assert.Empty(result.LoggedEvents.OfType()); - Assert.Empty(result.LoggedEvents.OfType()); + Assert.Single(result.LoggedEvents.OfType()); } /// /// Emulate a project with an unsupported language, and verify that - /// one warning is emitted because the assembly info file couldn't be generated. + /// no errors are emitted because the target is skipped. /// [Fact] - public async Task AssemblyInfo_NotProducedWithoutCodeDomProvider() + public async Task AssemblyInfo_Suppressed() { ProjectPropertyGroupElement propertyGroup = this.testProject.CreatePropertyGroupElement(); this.testProject.AppendChild(propertyGroup); propertyGroup.AddProperty("Language", "NoCodeDOMProviderForThisLanguage"); + propertyGroup.AddProperty(Properties.GenerateAssemblyVersionInfo, "false"); this.WriteVersionFile(); - BuildResults result = await this.BuildAsync(Targets.GenerateAssemblyVersionInfo, logVerbosity: LoggerVerbosity.Minimal, assertSuccessfulBuild: false); - Assert.Equal(BuildResultCode.Failure, result.BuildResult.OverallResult); + BuildResults result = await this.BuildAsync(Targets.GenerateAssemblyNBGVVersionInfo, logVerbosity: LoggerVerbosity.Minimal); string versionCsFilePath = Path.Combine(this.projectDirectory, result.BuildResult.ProjectStateAfterBuild.GetPropertyValue("VersionSourceFile")); Assert.False(File.Exists(versionCsFilePath)); - Assert.Single(result.LoggedEvents.OfType()); + Assert.Empty(result.LoggedEvents.OfType()); + Assert.Empty(result.LoggedEvents.OfType()); } /// @@ -142,74 +244,138 @@ public async Task AssemblyInfo_SuppressedImplicitlyByTargetExt() propertyGroup.AddProperty("TargetExt", ".notdll"); this.WriteVersionFile(); - BuildResults result = await this.BuildAsync(Targets.GenerateAssemblyVersionInfo, logVerbosity: LoggerVerbosity.Minimal); + BuildResults result = await this.BuildAsync(Targets.GenerateAssemblyNBGVVersionInfo, logVerbosity: LoggerVerbosity.Minimal); string versionCsFilePath = Path.Combine(this.projectDirectory, result.BuildResult.ProjectStateAfterBuild.GetPropertyValue("VersionSourceFile")); Assert.False(File.Exists(versionCsFilePath)); Assert.Empty(result.LoggedEvents.OfType()); Assert.Empty(result.LoggedEvents.OfType()); } - // TODO: add key container test. - [Theory] - [InlineData("keypair.snk", false)] - [InlineData("public.snk", true)] - [InlineData("protectedPair.pfx", true)] - public async Task AssemblyInfo_HasKeyData(string keyFile, bool delaySigned) + protected static Version GetExpectedAssemblyVersion(VersionOptions versionOptions, Version version) { - TestUtilities.ExtractEmbeddedResource($@"Keys\{keyFile}", Path.Combine(this.projectDirectory, keyFile)); - this.testProject.AddProperty("SignAssembly", "true"); - this.testProject.AddProperty("AssemblyOriginatorKeyFile", keyFile); - this.testProject.AddProperty("DelaySign", delaySigned.ToString()); - - this.WriteVersionFile(); - BuildResults result = await this.BuildAsync(Targets.GenerateAssemblyVersionInfo, logVerbosity: LoggerVerbosity.Minimal); - string versionCsContent = File.ReadAllText( - Path.GetFullPath( - Path.Combine( - this.projectDirectory, - result.BuildResult.ProjectStateAfterBuild.GetPropertyValue("VersionSourceFile")))); - this.Logger.WriteLine(versionCsContent); + // Function should be very similar to VersionOracle.GetAssemblyVersion() + Version assemblyVersion = (versionOptions?.AssemblyVersion?.Version ?? versionOptions.Version.Version).EnsureNonNegativeComponents(); - SyntaxTree sourceFile = CSharpSyntaxTree.ParseText(versionCsContent); - SyntaxNode syntaxTree = await sourceFile.GetRootAsync(); - IEnumerable fields = syntaxTree.DescendantNodes().OfType(); - - var publicKeyField = (LiteralExpressionSyntax)fields.SingleOrDefault(f => f.Identifier.ValueText == "PublicKey")?.Initializer.Value; - var publicKeyTokenField = (LiteralExpressionSyntax)fields.SingleOrDefault(f => f.Identifier.ValueText == "PublicKeyToken")?.Initializer.Value; - if (Path.GetExtension(keyFile) == ".pfx") + if (versionOptions?.AssemblyVersion?.Version is null) { - // No support for PFX (yet anyway), since they're encrypted. - // Note for future: I think by this point, the user has typically already decrypted - // the PFX and stored the key pair in a key container. If we knew how to find which one, - // we could perhaps divert to that. - Assert.Null(publicKeyField); - Assert.Null(publicKeyTokenField); + VersionOptions.VersionPrecision precision = versionOptions?.AssemblyVersion?.Precision ?? VersionOptions.DefaultVersionPrecision; + assemblyVersion = version; + + assemblyVersion = new Version( + assemblyVersion.Major, + precision >= VersionOptions.VersionPrecision.Minor ? assemblyVersion.Minor : 0, + precision >= VersionOptions.VersionPrecision.Build ? assemblyVersion.Build : 0, + precision >= VersionOptions.VersionPrecision.Revision ? assemblyVersion.Revision : 0); } - else + + return assemblyVersion; + } + + protected static RestoreEnvironmentVariables ApplyEnvironmentVariables(IReadOnlyDictionary variables) + { + Requires.NotNull(variables, nameof(variables)); + + var oldValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (KeyValuePair variable in variables) { - Assert.Equal( - "002400000480000094000000060200000024000052534131000400000100010067cea773679e0ecc114b7e1d442466a90bf77c755811a0d3962a546ed716525b6508abf9f78df132ffd3fb75fe604b3961e39c52d5dfc0e6c1fb233cb4fb56b1a9e3141513b23bea2cd156cb2ef7744e59ba6b663d1f5b2f9449550352248068e85b61c68681a6103cad91b3bf7a4b50d2fabf97e1d97ac34db65b25b58cd0dc", - publicKeyField?.Token.ValueText); - Assert.Equal("ca2d1515679318f5", publicKeyTokenField?.Token.ValueText); + oldValues[variable.Key] = Environment.GetEnvironmentVariable(variable.Key); + Environment.SetEnvironmentVariable(variable.Key, variable.Value); } - } - protected abstract void ApplyGlobalProperties(IDictionary globalProperties); + return new RestoreEnvironmentVariables(oldValues); + } - protected override void Dispose(bool disposing) + protected static string GetSemVerAppropriatePrereleaseTag(VersionOptions versionOptions) { - Environment.SetEnvironmentVariable("_NBGV_UnitTest", string.Empty); - base.Dispose(disposing); + return versionOptions.NuGetPackageVersionOrDefault.SemVer == 1 + ? versionOptions.Version.Prerelease?.Replace('.', '-') + : versionOptions.Version.Prerelease; } - protected ProjectRootElement CreateProjectRootElement(string projectDirectory, string projectName) + protected void AssertStandardProperties(VersionOptions versionOptions, BuildResults buildResult, string relativeProjectDirectory = null) { - using (var reader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream($"{ThisAssembly.RootNamespace}.test.prj"))) + int versionHeight = this.GetVersionHeight(relativeProjectDirectory); + Version idAsVersion = this.GetVersion(relativeProjectDirectory); + string commitIdShort = this.CommitIdShort; + Version version = this.GetVersion(relativeProjectDirectory); + Version assemblyVersion = GetExpectedAssemblyVersion(versionOptions, version); + IEnumerable additionalBuildMetadata = from item in buildResult.BuildResult.ProjectStateAfterBuild.GetItems("BuildMetadata") + select item.EvaluatedInclude; + string expectedBuildMetadata = $"+{commitIdShort}"; + if (additionalBuildMetadata.Any()) { - var pre = ProjectRootElement.Create(reader, this.projectCollection); - pre.FullPath = Path.Combine(projectDirectory, projectName); - pre.AddImport(Path.Combine(this.RepoPath, GitVersioningTargetsFileName)); - return pre; + expectedBuildMetadata += "." + string.Join(".", additionalBuildMetadata); + } + + string expectedBuildMetadataWithoutCommitId = additionalBuildMetadata.Any() ? $"+{string.Join(".", additionalBuildMetadata)}" : string.Empty; + string optionalFourthComponent = versionOptions.VersionHeightPosition == SemanticVersion.Position.Revision ? $".{idAsVersion.Revision}" : string.Empty; + + Assert.Equal($"{version}", buildResult.AssemblyFileVersion); + Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}{optionalFourthComponent}{versionOptions.Version.Prerelease}{expectedBuildMetadata}", buildResult.AssemblyInformationalVersion); + + // The assembly version property should always have four integer components to it, + // per bug https://github.com/dotnet/Nerdbank.GitVersioning/issues/26 + Assert.Equal($"{assemblyVersion.Major}.{assemblyVersion.Minor}.{assemblyVersion.Build}.{assemblyVersion.Revision}", buildResult.AssemblyVersion); + + Assert.Equal(idAsVersion.Build.ToString(), buildResult.BuildNumber); + Assert.Equal($"{version}", buildResult.BuildVersion); + Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}", buildResult.BuildVersion3Components); + Assert.Equal(idAsVersion.Build.ToString(), buildResult.BuildVersionNumberComponent); + Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}", buildResult.BuildVersionSimple); + Assert.Equal(this.LibGit2Repository.Head.Tip.Id.Sha, buildResult.GitCommitId); + Assert.Equal(this.LibGit2Repository.Head.Tip.Author.When.UtcTicks.ToString(CultureInfo.InvariantCulture), buildResult.GitCommitDateTicks); + Assert.Equal(commitIdShort, buildResult.GitCommitIdShort); + Assert.Equal(versionHeight.ToString(), buildResult.GitVersionHeight); + Assert.Equal($"{version.Major}.{version.Minor}", buildResult.MajorMinorVersion); + Assert.Equal(versionOptions.Version.Prerelease, buildResult.PrereleaseVersion); + Assert.Equal(expectedBuildMetadata, buildResult.SemVerBuildSuffix); + + string GetPkgVersionSuffix(bool useSemVer2) + { + string pkgVersionSuffix = buildResult.PublicRelease ? string.Empty : $"-g{commitIdShort}"; + if (useSemVer2) + { + pkgVersionSuffix += expectedBuildMetadataWithoutCommitId; + } + + return pkgVersionSuffix; + } + + // NuGet is now SemVer 2.0 and will pass additional build metadata if provided + string nugetPkgVersionSuffix = GetPkgVersionSuffix(useSemVer2: versionOptions?.NuGetPackageVersionOrDefault.SemVer == 2); + Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}{GetSemVerAppropriatePrereleaseTag(versionOptions)}{nugetPkgVersionSuffix}", buildResult.NuGetPackageVersion); + + // Chocolatey only supports SemVer 1.0 + string chocolateyPkgVersionSuffix = GetPkgVersionSuffix(useSemVer2: false); + Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}{GetSemVerAppropriatePrereleaseTag(versionOptions)}{chocolateyPkgVersionSuffix}", buildResult.ChocolateyPackageVersion); + + VersionOptions.CloudBuildNumberOptions buildNumberOptions = versionOptions.CloudBuildOrDefault.BuildNumberOrDefault; + if (buildNumberOptions.EnabledOrDefault) + { + VersionOptions.CloudBuildNumberCommitIdOptions commitIdOptions = buildNumberOptions.IncludeCommitIdOrDefault; + var buildNumberSemVer = SemanticVersion.Parse(buildResult.CloudBuildNumber); + bool hasCommitData = commitIdOptions.WhenOrDefault == VersionOptions.CloudBuildNumberCommitWhen.Always + || (commitIdOptions.WhenOrDefault == VersionOptions.CloudBuildNumberCommitWhen.NonPublicReleaseOnly && !buildResult.PublicRelease); + Version expectedVersion = hasCommitData && commitIdOptions.WhereOrDefault == VersionOptions.CloudBuildNumberCommitWhere.FourthVersionComponent + ? idAsVersion + : new Version(version.Major, version.Minor, version.Build); + Assert.Equal(expectedVersion, buildNumberSemVer.Version); + Assert.Equal(buildResult.PrereleaseVersion, buildNumberSemVer.Prerelease); + string expectedBuildNumberMetadata = hasCommitData && commitIdOptions.WhereOrDefault == VersionOptions.CloudBuildNumberCommitWhere.BuildMetadata + ? $"+{commitIdShort}" + : string.Empty; + if (additionalBuildMetadata.Any()) + { + expectedBuildNumberMetadata = expectedBuildNumberMetadata.Length == 0 + ? "+" + string.Join(".", additionalBuildMetadata) + : expectedBuildNumberMetadata + "." + string.Join(".", additionalBuildMetadata); + } + + Assert.Equal(expectedBuildNumberMetadata, buildNumberSemVer.BuildMetadata); + } + else + { + Assert.Equal(string.Empty, buildResult.CloudBuildNumber); } } @@ -237,6 +403,65 @@ protected async Task BuildAsync(string target = Targets.GetBuildVe return result; } + protected ProjectRootElement CreateNativeProjectRootElement(string projectDirectory, string projectName) + { + using (var reader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream($"{ThisAssembly.RootNamespace}.test.vcprj"))) + { + var pre = ProjectRootElement.Create(reader, this.projectCollection); + pre.FullPath = Path.Combine(projectDirectory, projectName); + pre.InsertAfterChild(pre.CreateImportElement(Path.Combine(this.RepoPath, GitVersioningPropsFileName)), null); + pre.AddImport(Path.Combine(this.RepoPath, GitVersioningTargetsFileName)); + return pre; + } + } + + protected ProjectRootElement CreateProjectRootElement(string projectDirectory, string projectName) + { + using (var reader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream($"{ThisAssembly.RootNamespace}.test.prj"))) + { + var pre = ProjectRootElement.Create(reader, this.projectCollection); + pre.FullPath = Path.Combine(projectDirectory, projectName); + pre.InsertAfterChild(pre.CreateImportElement(Path.Combine(this.RepoPath, GitVersioningPropsFileName)), null); + pre.AddImport(Path.Combine(this.RepoPath, GitVersioningTargetsFileName)); + return pre; + } + } + + protected void MakeItAVBProject() + { + ProjectImportElement csharpImport = this.testProject.Imports.Single(i => i.Project.Contains("CSharp")); + csharpImport.Project = "$(MSBuildToolsPath)/Microsoft.VisualBasic.targets"; + ProjectPropertyElement isVbProperty = this.testProject.Properties.Single(p => p.Name == "IsVB"); + isVbProperty.Value = "true"; + } + + protected abstract void ApplyGlobalProperties(IDictionary globalProperties); + + /// + protected override void Dispose(bool disposing) + { + Environment.SetEnvironmentVariable("_NBGV_UnitTest", string.Empty); + base.Dispose(disposing); + } + + private void LoadTargetsIntoProjectCollection() + { + string prefix = $"{ThisAssembly.RootNamespace}.Targets."; + + IEnumerable streamNames = from name in Assembly.GetExecutingAssembly().GetManifestResourceNames() + where name.StartsWith(prefix, StringComparison.Ordinal) + select name; + foreach (string name in streamNames) + { + using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name)) + { + var targetsFile = ProjectRootElement.Create(XmlReader.Create(stream), this.projectCollection); + targetsFile.FullPath = Path.Combine(this.RepoPath, name.Substring(prefix.Length)); + targetsFile.Save(); // persist files on disk + } + } + } + private void Init() { int seed = (int)DateTime.Now.Ticks; @@ -262,31 +487,61 @@ private void Init() } } - private void LoadTargetsIntoProjectCollection() + protected struct RestoreEnvironmentVariables : IDisposable { - string prefix = $"{ThisAssembly.RootNamespace}.Targets."; + private readonly IReadOnlyDictionary applyVariables; - IEnumerable streamNames = from name in Assembly.GetExecutingAssembly().GetManifestResourceNames() - where name.StartsWith(prefix, StringComparison.Ordinal) - select name; - foreach (string name in streamNames) + internal RestoreEnvironmentVariables(IReadOnlyDictionary applyVariables) { - using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name)) - { - var targetsFile = ProjectRootElement.Create(XmlReader.Create(stream), this.projectCollection); - targetsFile.FullPath = Path.Combine(this.RepoPath, name.Substring(prefix.Length)); - targetsFile.Save(); // persist files on disk - } + this.applyVariables = applyVariables; + } + + public void Dispose() + { + ApplyEnvironmentVariables(this.applyVariables); } } + protected static class CloudBuild + { + public static readonly ImmutableDictionary SuppressEnvironment = ImmutableDictionary.Empty + + // AppVeyor + .Add("APPVEYOR", string.Empty) + .Add("APPVEYOR_REPO_TAG", string.Empty) + .Add("APPVEYOR_REPO_TAG_NAME", string.Empty) + .Add("APPVEYOR_PULL_REQUEST_NUMBER", string.Empty) + + // VSTS + .Add("SYSTEM_TEAMPROJECTID", string.Empty) + .Add("BUILD_SOURCEBRANCH", string.Empty) + + // Teamcity + .Add("BUILD_VCS_NUMBER", string.Empty) + .Add("BUILD_GIT_BRANCH", string.Empty); + + public static readonly ImmutableDictionary VSTS = SuppressEnvironment + .SetItem("SYSTEM_TEAMPROJECTID", "1"); + + public static readonly ImmutableDictionary AppVeyor = SuppressEnvironment + .SetItem("APPVEYOR", "True"); + + public static readonly ImmutableDictionary Teamcity = SuppressEnvironment + .SetItem("BUILD_VCS_NUMBER", "1"); + } + protected static class Targets { internal const string Build = "Build"; internal const string GetBuildVersion = "GetBuildVersion"; internal const string GetNuGetPackageVersion = "GetNuGetPackageVersion"; - internal const string GenerateAssemblyVersionInfo = "GenerateAssemblyNBGVVersionInfo"; - internal const string GenerateNativeVersionInfo = "GenerateNativeVersionInfo"; + internal const string GenerateAssemblyNBGVVersionInfo = "GenerateAssemblyNBGVVersionInfo"; + internal const string GenerateNativeNBGVVersionInfo = "GenerateNativeNBGVVersionInfo"; + } + + protected static class Properties + { + internal const string GenerateAssemblyVersionInfo = "GenerateAssemblyVersionInfo"; } protected class BuildResults @@ -381,7 +636,7 @@ public override string ToString() } } - protected class MSBuildLogger : ILogger + private class MSBuildLogger : ILogger { public string Parameters { get; set; } diff --git a/test/Nerdbank.GitVersioning.Tests/SomeGitBuildIntegrationTests.cs b/test/Nerdbank.GitVersioning.Tests/SomeGitBuildIntegrationTests.cs index 38777be0..5ced01af 100644 --- a/test/Nerdbank.GitVersioning.Tests/SomeGitBuildIntegrationTests.cs +++ b/test/Nerdbank.GitVersioning.Tests/SomeGitBuildIntegrationTests.cs @@ -27,60 +27,6 @@ protected SomeGitBuildIntegrationTests(ITestOutputHelper logger) { } - public static object[][] CloudBuildVariablesData - { - get - { - return new object[][] - { - new object[] { CloudBuild.VSTS, "##vso[task.setvariable variable={NAME};]{VALUE}", false }, - new object[] { CloudBuild.VSTS, "##vso[task.setvariable variable={NAME};]{VALUE}", true }, - }; - } - } - - public static object[][] BuildNumberData - { - get - { - return new object[][] - { - new object[] { BuildNumberVersionOptionsBasis, CloudBuild.VSTS, "##vso[build.updatebuildnumber]{CLOUDBUILDNUMBER}" }, - }; - } - } - - private static VersionOptions BuildNumberVersionOptionsBasis - { - get - { - return new VersionOptions - { - Version = SemanticVersion.Parse("1.0"), - CloudBuild = new VersionOptions.CloudBuildOptions - { - BuildNumber = new VersionOptions.CloudBuildNumberOptions - { - Enabled = true, - IncludeCommitId = new VersionOptions.CloudBuildNumberCommitIdOptions(), - }, - }, - }; - } - } - - public static IEnumerable CloudBuildOfBranch(string branchName) - { - return new object[][] - { - new object[] { CloudBuild.AppVeyor.SetItem("APPVEYOR_REPO_BRANCH", branchName) }, - new object[] { CloudBuild.VSTS.SetItem("BUILD_SOURCEBRANCH", $"refs/heads/{branchName}") }, - new object[] { CloudBuild.VSTS.SetItem("BUILD_SOURCEBRANCH", $"refs/tags/{branchName}") }, - new object[] { CloudBuild.Teamcity.SetItem("BUILD_GIT_BRANCH", $"refs/heads/{branchName}") }, - new object[] { CloudBuild.Teamcity.SetItem("BUILD_GIT_BRANCH", $"refs/tags/{branchName}") }, - }; - } - [Fact] public async Task GetBuildVersion_WithThreeVersionIntegers() { @@ -440,7 +386,7 @@ public async Task PublicRelease_RegEx_SatisfiedByCI(IReadOnlyDictionary properties, string expectedMessage, bool setAllVariables) { @@ -498,7 +444,9 @@ public async Task CloudBuildVariables_SetInCI(IReadOnlyDictionary string.Equals(e.Message, $"n1=v1", StringComparison.OrdinalIgnoreCase)); + Assert.Contains( + buildResult.LoggedEvents, + e => string.Equals(e.Message, $"n1=v1", StringComparison.OrdinalIgnoreCase) || string.Equals(e.Message, $"n1='v1'", StringComparison.OrdinalIgnoreCase)); versionOptions.CloudBuild.SetVersionVariables = false; this.WriteVersionFile(versionOptions); @@ -733,7 +681,7 @@ public async Task AssemblyInfo(bool isVB, bool includeNonVersionAttributes, bool } [Fact] - [Trait("TestCategory", "FailsOnAzurePipelines")] + [Trait("TestCategory", "FailsInCloudTest")] public async Task AssemblyInfo_IncrementalBuild() { this.WriteVersionFile(prerelease: "-beta"); @@ -762,190 +710,11 @@ public async Task NativeVersionInfo_CreateNativeResourceDll() Assert.Equal("1.2", fileInfo.FileVersion); Assert.Equal("1.2.0", fileInfo.ProductVersion); Assert.Equal("test", fileInfo.InternalName); - Assert.Equal("NerdBank", fileInfo.CompanyName); + Assert.Equal("Nerdbank", fileInfo.CompanyName); Assert.Equal($"Copyright (c) {DateTime.Now.Year}. All rights reserved.", fileInfo.LegalCopyright); } #endif + /// protected override GitContext CreateGitContext(string path, string committish = null) => throw new NotImplementedException(); - - private static Version GetExpectedAssemblyVersion(VersionOptions versionOptions, Version version) - { - // Function should be very similar to VersionOracle.GetAssemblyVersion() - Version assemblyVersion = (versionOptions?.AssemblyVersion?.Version ?? versionOptions.Version.Version).EnsureNonNegativeComponents(); - VersionOptions.VersionPrecision precision = versionOptions?.AssemblyVersion?.Precision ?? VersionOptions.DefaultVersionPrecision; - - assemblyVersion = new System.Version( - assemblyVersion.Major, - precision >= VersionOptions.VersionPrecision.Minor ? assemblyVersion.Minor : 0, - precision >= VersionOptions.VersionPrecision.Build ? version.Build : 0, - precision >= VersionOptions.VersionPrecision.Revision ? version.Revision : 0); - return assemblyVersion; - } - - private static RestoreEnvironmentVariables ApplyEnvironmentVariables(IReadOnlyDictionary variables) - { - Requires.NotNull(variables, nameof(variables)); - - var oldValues = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (KeyValuePair variable in variables) - { - oldValues[variable.Key] = Environment.GetEnvironmentVariable(variable.Key); - Environment.SetEnvironmentVariable(variable.Key, variable.Value); - } - - return new RestoreEnvironmentVariables(oldValues); - } - - private static string GetSemVerAppropriatePrereleaseTag(VersionOptions versionOptions) - { - return versionOptions.NuGetPackageVersionOrDefault.SemVer == 1 - ? versionOptions.Version.Prerelease?.Replace('.', '-') - : versionOptions.Version.Prerelease; - } - - private void AssertStandardProperties(VersionOptions versionOptions, BuildResults buildResult, string relativeProjectDirectory = null) - { - int versionHeight = this.GetVersionHeight(relativeProjectDirectory); - Version idAsVersion = this.GetVersion(relativeProjectDirectory); - string commitIdShort = this.CommitIdShort; - Version version = this.GetVersion(relativeProjectDirectory); - Version assemblyVersion = GetExpectedAssemblyVersion(versionOptions, version); - IEnumerable additionalBuildMetadata = from item in buildResult.BuildResult.ProjectStateAfterBuild.GetItems("BuildMetadata") - select item.EvaluatedInclude; - string expectedBuildMetadata = $"+{commitIdShort}"; - if (additionalBuildMetadata.Any()) - { - expectedBuildMetadata += "." + string.Join(".", additionalBuildMetadata); - } - - string expectedBuildMetadataWithoutCommitId = additionalBuildMetadata.Any() ? $"+{string.Join(".", additionalBuildMetadata)}" : string.Empty; - - Assert.Equal($"{version}", buildResult.AssemblyFileVersion); - Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}{versionOptions.Version.Prerelease}{expectedBuildMetadata}", buildResult.AssemblyInformationalVersion); - - // The assembly version property should always have four integer components to it, - // per bug https://github.com/dotnet/Nerdbank.GitVersioning/issues/26 - Assert.Equal($"{assemblyVersion.Major}.{assemblyVersion.Minor}.{assemblyVersion.Build}.{assemblyVersion.Revision}", buildResult.AssemblyVersion); - - Assert.Equal(idAsVersion.Build.ToString(), buildResult.BuildNumber); - Assert.Equal($"{version}", buildResult.BuildVersion); - Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}", buildResult.BuildVersion3Components); - Assert.Equal(idAsVersion.Build.ToString(), buildResult.BuildVersionNumberComponent); - Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}", buildResult.BuildVersionSimple); - Assert.Equal(this.LibGit2Repository.Head.Tip.Id.Sha, buildResult.GitCommitId); - Assert.Equal(this.LibGit2Repository.Head.Tip.Author.When.UtcTicks.ToString(CultureInfo.InvariantCulture), buildResult.GitCommitDateTicks); - Assert.Equal(commitIdShort, buildResult.GitCommitIdShort); - Assert.Equal(versionHeight.ToString(), buildResult.GitVersionHeight); - Assert.Equal($"{version.Major}.{version.Minor}", buildResult.MajorMinorVersion); - Assert.Equal(versionOptions.Version.Prerelease, buildResult.PrereleaseVersion); - Assert.Equal(expectedBuildMetadata, buildResult.SemVerBuildSuffix); - - string GetPkgVersionSuffix(bool useSemVer2) - { - string pkgVersionSuffix = buildResult.PublicRelease ? string.Empty : $"-g{commitIdShort}"; - if (useSemVer2) - { - pkgVersionSuffix += expectedBuildMetadataWithoutCommitId; - } - - return pkgVersionSuffix; - } - - // NuGet is now SemVer 2.0 and will pass additional build metadata if provided - string nugetPkgVersionSuffix = GetPkgVersionSuffix(useSemVer2: versionOptions?.NuGetPackageVersionOrDefault.SemVer == 2); - Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}{GetSemVerAppropriatePrereleaseTag(versionOptions)}{nugetPkgVersionSuffix}", buildResult.NuGetPackageVersion); - - // Chocolatey only supports SemVer 1.0 - string chocolateyPkgVersionSuffix = GetPkgVersionSuffix(useSemVer2: false); - Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}{GetSemVerAppropriatePrereleaseTag(versionOptions)}{chocolateyPkgVersionSuffix}", buildResult.ChocolateyPackageVersion); - - VersionOptions.CloudBuildNumberOptions buildNumberOptions = versionOptions.CloudBuildOrDefault.BuildNumberOrDefault; - if (buildNumberOptions.EnabledOrDefault) - { - VersionOptions.CloudBuildNumberCommitIdOptions commitIdOptions = buildNumberOptions.IncludeCommitIdOrDefault; - var buildNumberSemVer = SemanticVersion.Parse(buildResult.CloudBuildNumber); - bool hasCommitData = commitIdOptions.WhenOrDefault == VersionOptions.CloudBuildNumberCommitWhen.Always - || (commitIdOptions.WhenOrDefault == VersionOptions.CloudBuildNumberCommitWhen.NonPublicReleaseOnly && !buildResult.PublicRelease); - Version expectedVersion = hasCommitData && commitIdOptions.WhereOrDefault == VersionOptions.CloudBuildNumberCommitWhere.FourthVersionComponent - ? idAsVersion - : new Version(version.Major, version.Minor, version.Build); - Assert.Equal(expectedVersion, buildNumberSemVer.Version); - Assert.Equal(buildResult.PrereleaseVersion, buildNumberSemVer.Prerelease); - string expectedBuildNumberMetadata = hasCommitData && commitIdOptions.WhereOrDefault == VersionOptions.CloudBuildNumberCommitWhere.BuildMetadata - ? $"+{commitIdShort}" - : string.Empty; - if (additionalBuildMetadata.Any()) - { - expectedBuildNumberMetadata = expectedBuildNumberMetadata.Length == 0 - ? "+" + string.Join(".", additionalBuildMetadata) - : expectedBuildNumberMetadata + "." + string.Join(".", additionalBuildMetadata); - } - - Assert.Equal(expectedBuildNumberMetadata, buildNumberSemVer.BuildMetadata); - } - else - { - Assert.Equal(string.Empty, buildResult.CloudBuildNumber); - } - } - - private ProjectRootElement CreateNativeProjectRootElement(string projectDirectory, string projectName) - { - using (var reader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream($"{ThisAssembly.RootNamespace}.test.vcprj"))) - { - var pre = ProjectRootElement.Create(reader, this.projectCollection); - pre.FullPath = Path.Combine(projectDirectory, projectName); - pre.AddImport(Path.Combine(this.RepoPath, GitVersioningTargetsFileName)); - return pre; - } - } - - private void MakeItAVBProject() - { - ProjectImportElement csharpImport = this.testProject.Imports.Single(i => i.Project.Contains("CSharp")); - csharpImport.Project = "$(MSBuildToolsPath)/Microsoft.VisualBasic.targets"; - ProjectPropertyElement isVbProperty = this.testProject.Properties.Single(p => p.Name == "IsVB"); - isVbProperty.Value = "true"; - } - - private struct RestoreEnvironmentVariables : IDisposable - { - private readonly IReadOnlyDictionary applyVariables; - - internal RestoreEnvironmentVariables(IReadOnlyDictionary applyVariables) - { - this.applyVariables = applyVariables; - } - - public void Dispose() - { - ApplyEnvironmentVariables(this.applyVariables); - } - } - - private static class CloudBuild - { - public static readonly ImmutableDictionary SuppressEnvironment = ImmutableDictionary.Empty - // AppVeyor - .Add("APPVEYOR", string.Empty) - .Add("APPVEYOR_REPO_TAG", string.Empty) - .Add("APPVEYOR_REPO_TAG_NAME", string.Empty) - .Add("APPVEYOR_PULL_REQUEST_NUMBER", string.Empty) - // VSTS - .Add("SYSTEM_TEAMPROJECTID", string.Empty) - .Add("BUILD_SOURCEBRANCH", string.Empty) - // Teamcity - .Add("BUILD_VCS_NUMBER", string.Empty) - .Add("BUILD_GIT_BRANCH", string.Empty); - - public static readonly ImmutableDictionary VSTS = SuppressEnvironment - .SetItem("SYSTEM_TEAMPROJECTID", "1"); - - public static readonly ImmutableDictionary AppVeyor = SuppressEnvironment - .SetItem("APPVEYOR", "True"); - - public static readonly ImmutableDictionary Teamcity = SuppressEnvironment - .SetItem("BUILD_VCS_NUMBER", "1"); - } }