Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 113 additions & 68 deletions src/NerdBank.GitVersioning.Tests/BuildIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
public class BuildIntegrationTests : RepoTestBase
{
private const string GitVersioningTargetsFileName = "NerdBank.GitVersioning.targets";
private const string UnitTestCloudBuildPrefix = "UnitTest: ";
private static readonly string[] ToxicEnvironmentVariablePrefixes = new string[]
{
"APPVEYOR",
Expand All @@ -42,7 +43,6 @@ public class BuildIntegrationTests : RepoTestBase
// Set global properties to neutralize environment variables
// that might actually be defined by a CI that is building and running these tests.
{ "PublicRelease", string.Empty },
{ "_NBGV_UnitTest", "true" }
};
private Random random;

Expand All @@ -59,6 +59,7 @@ public BuildIntegrationTests(ITestOutputHelper logger)
this.LoadTargetsIntoProjectCollection();
this.testProject = this.CreateProjectRootElement(this.projectDirectory, "test.proj");
this.globalProperties.Add("NerdbankGitVersioningTasksPath", Environment.CurrentDirectory + "\\");
Environment.SetEnvironmentVariable("_NBGV_UnitTest", "true");

// Sterilize the test of any environment variables.
foreach (System.Collections.DictionaryEntry variable in Environment.GetEnvironmentVariables())
Expand All @@ -71,6 +72,12 @@ public BuildIntegrationTests(ITestOutputHelper logger)
}
}

protected override void Dispose(bool disposing)
{
Environment.SetEnvironmentVariable("_NBGV_UnitTest", string.Empty);
base.Dispose(disposing);
}

[Fact]
public async Task GetBuildVersion_Returns_BuildVersion_Property()
{
Expand Down Expand Up @@ -104,7 +111,7 @@ public async Task GetBuildVersion_OutsideGit_PointingToGit()

// Write the same version file to the 'real' repo
this.WriteVersionFile(version);

// Point the project to the 'real' repo
this.testProject.AddProperty("GitRepoRoot", this.RepoPath);

Expand Down Expand Up @@ -362,8 +369,8 @@ public static IEnumerable<object[]> CloudBuildOfBranch(string branchName)
{
return new object[][]
{
new object[] { CloudBuild.AppVeyor.Add("APPVEYOR_REPO_BRANCH", branchName) },
new object[] { CloudBuild.VSTS.Add( "BUILD_SOURCEBRANCH", $"refs/heads/{branchName}") },
new object[] { CloudBuild.AppVeyor.SetItem("APPVEYOR_REPO_BRANCH", branchName) },
new object[] { CloudBuild.VSTS.SetItem( "BUILD_SOURCEBRANCH", $"refs/heads/{branchName}") },
};
}

Expand All @@ -382,14 +389,12 @@ public async Task PublicRelease_RegEx_SatisfiedByCI(IReadOnlyDictionary<string,
// Don't actually switch the checked out branch in git. CI environment variables
// should take precedence over actual git configuration. (Why? because these variables may
// retain information about which tag was checked out on a detached head).
foreach (var property in serverProperties)
using (ApplyEnvironmentVariables(serverProperties))
{
this.globalProperties[property.Key] = property.Value;
var buildResult = await this.BuildAsync();
Assert.True(buildResult.PublicRelease);
AssertStandardProperties(versionOptions, buildResult);
}

var buildResult = await this.BuildAsync();
Assert.True(buildResult.PublicRelease);
AssertStandardProperties(versionOptions, buildResult);
}

public static object[][] CloudBuildVariablesData
Expand All @@ -407,44 +412,42 @@ public static object[][] CloudBuildVariablesData
[MemberData(nameof(CloudBuildVariablesData))]
public async Task CloudBuildVariables_SetInCI(IReadOnlyDictionary<string, string> properties, string expectedMessage)
{
foreach (var property in properties)
{
this.globalProperties[property.Key] = property.Value;
}

string keyName = "n1";
string value = "v1";
this.testProject.AddItem("CloudBuildVersionVars", keyName, new Dictionary<string, string> { { "Value", value } });

string alwaysExpectedMessage = expectedMessage
.Replace("{NAME}", keyName)
.Replace("{VALUE}", value);

var versionOptions = new VersionOptions
using (ApplyEnvironmentVariables(properties))
{
Version = SemanticVersion.Parse("1.0"),
CloudBuild = new VersionOptions.CloudBuildOptions { SetVersionVariables = true },
};
this.WriteVersionFile(versionOptions);
this.InitializeSourceControl();
string keyName = "n1";
string value = "v1";
this.testProject.AddItem("CloudBuildVersionVars", keyName, new Dictionary<string, string> { { "Value", value } });

var buildResult = await this.BuildAsync();
AssertStandardProperties(versionOptions, buildResult);
string conditionallyExpectedMessage = expectedMessage
.Replace("{NAME}", "GitBuildVersion")
.Replace("{VALUE}", buildResult.BuildVersion);
Assert.Contains(alwaysExpectedMessage, buildResult.LoggedEvents.Select(e => e.Message.TrimEnd()));
Assert.Contains(conditionallyExpectedMessage, buildResult.LoggedEvents.Select(e => e.Message.TrimEnd()));
string alwaysExpectedMessage = UnitTestCloudBuildPrefix + expectedMessage
.Replace("{NAME}", keyName)
.Replace("{VALUE}", value);

versionOptions.CloudBuild.SetVersionVariables = false;
this.WriteVersionFile(versionOptions);
buildResult = await this.BuildAsync();
AssertStandardProperties(versionOptions, buildResult);
conditionallyExpectedMessage = expectedMessage
.Replace("{NAME}", "GitBuildVersion")
.Replace("{VALUE}", buildResult.BuildVersion);
Assert.Contains(alwaysExpectedMessage, buildResult.LoggedEvents.Select(e => e.Message.TrimEnd()));
Assert.DoesNotContain(conditionallyExpectedMessage, buildResult.LoggedEvents.Select(e => e.Message.TrimEnd()));
var versionOptions = new VersionOptions
{
Version = SemanticVersion.Parse("1.0"),
CloudBuild = new VersionOptions.CloudBuildOptions { SetVersionVariables = true },
};
this.WriteVersionFile(versionOptions);
this.InitializeSourceControl();

var buildResult = await this.BuildAsync();
AssertStandardProperties(versionOptions, buildResult);
string conditionallyExpectedMessage = UnitTestCloudBuildPrefix + expectedMessage
.Replace("{NAME}", "GitBuildVersion")
.Replace("{VALUE}", buildResult.BuildVersion);
Assert.Contains(alwaysExpectedMessage, buildResult.LoggedEvents.Select(e => e.Message.TrimEnd()));
Assert.Contains(conditionallyExpectedMessage, buildResult.LoggedEvents.Select(e => e.Message.TrimEnd()));

versionOptions.CloudBuild.SetVersionVariables = false;
this.WriteVersionFile(versionOptions);
buildResult = await this.BuildAsync();
AssertStandardProperties(versionOptions, buildResult);
conditionallyExpectedMessage = UnitTestCloudBuildPrefix + expectedMessage
.Replace("{NAME}", "GitBuildVersion")
.Replace("{VALUE}", buildResult.BuildVersion);
Assert.Contains(alwaysExpectedMessage, buildResult.LoggedEvents.Select(e => e.Message.TrimEnd()));
Assert.DoesNotContain(conditionallyExpectedMessage, buildResult.LoggedEvents.Select(e => e.Message.TrimEnd()));
}
}

private static VersionOptions BuildNumberVersionOptionsBasis
Expand Down Expand Up @@ -483,16 +486,23 @@ public async Task BuildNumber_SetInCI(VersionOptions versionOptions, IReadOnlyDi
{
this.WriteVersionFile(versionOptions);
this.InitializeSourceControl();

foreach (var property in properties)
using (ApplyEnvironmentVariables(properties))
{
this.globalProperties[property.Key] = property.Value;
var buildResult = await this.BuildAsync();
AssertStandardProperties(versionOptions, buildResult);
expectedBuildNumberMessage = expectedBuildNumberMessage.Replace("{CLOUDBUILDNUMBER}", buildResult.CloudBuildNumber);
Assert.Contains(UnitTestCloudBuildPrefix + expectedBuildNumberMessage, buildResult.LoggedEvents.Select(e => e.Message.TrimEnd()));
}

var buildResult = await this.BuildAsync();
AssertStandardProperties(versionOptions, buildResult);
expectedBuildNumberMessage = expectedBuildNumberMessage.Replace("{CLOUDBUILDNUMBER}", buildResult.CloudBuildNumber);
Assert.Contains(expectedBuildNumberMessage, buildResult.LoggedEvents.Select(e => e.Message.TrimEnd()));
versionOptions.CloudBuild.BuildNumber.Enabled = false;
this.WriteVersionFile(versionOptions);
using (ApplyEnvironmentVariables(properties))
{
var buildResult = await this.BuildAsync();
AssertStandardProperties(versionOptions, buildResult);
expectedBuildNumberMessage = expectedBuildNumberMessage.Replace("{CLOUDBUILDNUMBER}", buildResult.CloudBuildNumber);
Assert.DoesNotContain(UnitTestCloudBuildPrefix + expectedBuildNumberMessage, buildResult.LoggedEvents.Select(e => e.Message.TrimEnd()));
}
}

[Theory]
Expand Down Expand Up @@ -526,12 +536,15 @@ public async Task PublicRelease_RegEx_SatisfiedByCheckedOutBranch()
this.WriteVersionFile(versionOptions);
this.InitializeSourceControl();

// Check out a branch that conforms.
var releaseBranch = this.Repo.CreateBranch("release");
this.Repo.Checkout(releaseBranch);
var buildResult = await this.BuildAsync();
Assert.True(buildResult.PublicRelease);
AssertStandardProperties(versionOptions, buildResult);
using (ApplyEnvironmentVariables(CloudBuild.SuppressEnvironment))
{
// Check out a branch that conforms.
var releaseBranch = this.Repo.CreateBranch("release");
this.Repo.Checkout(releaseBranch);
var buildResult = await this.BuildAsync();
Assert.True(buildResult.PublicRelease);
AssertStandardProperties(versionOptions, buildResult);
}
}

[Theory]
Expand Down Expand Up @@ -705,6 +718,20 @@ private static Version GetExpectedAssemblyVersion(VersionOptions versionOptions,
return assemblyVersion;
}

private static RestoreEnvironmentVariables ApplyEnvironmentVariables(IReadOnlyDictionary<string, string> variables)
{
Requires.NotNull(variables, nameof(variables));

var oldValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var variable in variables)
{
oldValues[variable.Key] = Environment.GetEnvironmentVariable(variable.Key);
Environment.SetEnvironmentVariable(variable.Key, variable.Value);
}

return new RestoreEnvironmentVariables(oldValues);
}

private void AssertStandardProperties(VersionOptions versionOptions, BuildResults buildResult, string relativeProjectDirectory = null)
{
int versionHeight = this.Repo.GetVersionHeight(relativeProjectDirectory);
Expand All @@ -728,9 +755,6 @@ private void AssertStandardProperties(VersionOptions versionOptions, BuildResult
Assert.Equal($"{assemblyVersion.Major}.{assemblyVersion.Minor}.{assemblyVersion.Build}.{assemblyVersion.Revision}", buildResult.AssemblyVersion);

Assert.Equal(idAsVersion.Build.ToString(), buildResult.BuildNumber);
Assert.Equal(idAsVersion.Build.ToString(), buildResult.BuildNumberFirstAndSecondComponentsIfApplicable);
Assert.Equal(idAsVersion.Build.ToString(), buildResult.BuildNumberFirstComponent);
Assert.Equal(string.Empty, buildResult.BuildNumberSecondComponent);
Assert.Equal($"{version}", buildResult.BuildVersion);
Assert.Equal($"{idAsVersion.Major}.{idAsVersion.Minor}.{idAsVersion.Build}", buildResult.BuildVersion3Components);
Assert.Equal(idAsVersion.Build.ToString(), buildResult.BuildVersionNumberComponent);
Expand Down Expand Up @@ -846,12 +870,36 @@ private void MakeItAVBProject()
csharpImport.Project = @"$(MSBuildToolsPath)\Microsoft.VisualBasic.targets";
}

private struct RestoreEnvironmentVariables : IDisposable
{
private readonly IReadOnlyDictionary<string, string> applyVariables;

internal RestoreEnvironmentVariables(IReadOnlyDictionary<string, string> applyVariables)
{
this.applyVariables = applyVariables;
}

public void Dispose()
{
ApplyEnvironmentVariables(this.applyVariables);
}
}

private static class CloudBuild
{
public static readonly ImmutableDictionary<string, string> VSTS = ImmutableDictionary<string, string>.Empty
.Add("SYSTEM_TEAMPROJECTID", "1");
public static readonly ImmutableDictionary<string, string> AppVeyor = ImmutableDictionary<string, string>.Empty
.Add("APPVEYOR", "True");
public static readonly ImmutableDictionary<string, string> SuppressEnvironment = ImmutableDictionary<string, string>.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);
public static readonly ImmutableDictionary<string, string> VSTS = SuppressEnvironment
.SetItem("SYSTEM_TEAMPROJECTID", "1");
public static readonly ImmutableDictionary<string, string> AppVeyor = SuppressEnvironment
.SetItem("APPVEYOR", "True");
}

private static class Targets
Expand Down Expand Up @@ -882,9 +930,6 @@ internal BuildResults(BuildResult buildResult, IReadOnlyList<BuildEventArgs> log
public string PrereleaseVersion => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("PrereleaseVersion");
public string MajorMinorVersion => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("MajorMinorVersion");
public string BuildVersionNumberComponent => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("BuildVersionNumberComponent");
public string BuildNumberFirstComponent => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("BuildNumberFirstComponent");
public string BuildNumberSecondComponent => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("BuildNumberSecondComponent");
public string BuildNumberFirstAndSecondComponentsIfApplicable => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("BuildNumberFirstAndSecondComponentsIfApplicable");
public string GitCommitIdShort => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("GitCommitIdShort");
public string GitVersionHeight => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("GitVersionHeight");
public string SemVerBuildSuffix => this.BuildResult.ProjectStateAfterBuild.GetPropertyValue("SemVerBuildSuffix");
Expand Down
3 changes: 2 additions & 1 deletion src/NerdBank.GitVersioning.Tests/RepoTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using LibGit2Sharp;
using Validation;
using Xunit.Abstractions;
using System.Diagnostics;

public abstract class RepoTestBase : IDisposable
{
public RepoTestBase(ITestOutputHelper logger)
Expand Down
25 changes: 25 additions & 0 deletions src/NerdBank.GitVersioning/CloudBuild.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Nerdbank.GitVersioning
{
using System.Linq;
using CloudBuildServices;

/// <summary>
/// Provides access to cloud build providers.
/// </summary>
public static class CloudBuild
{
/// <summary>
/// An array of cloud build systems we support.
/// </summary>
private static readonly ICloudBuild[] SupportedCloudBuilds = new ICloudBuild[] {
new AppVeyor(),
new VisualStudioTeamServices(),
new TeamCity(),
};

/// <summary>
/// Gets the cloud build provider that applies to this build, if any.
/// </summary>
public static ICloudBuild Active => SupportedCloudBuilds.FirstOrDefault(cb => cb.IsApplicable);
}
}
Loading