|
| 1 | +// ----------------------------------------------------------------------- |
| 2 | +// <copyright file="AssemblyValidationTests.cs" company="Ubiquity.NET Contributors"> |
| 3 | +// Copyright (c) Ubiquity.NET Contributors. All rights reserved. |
| 4 | +// </copyright> |
| 5 | +// ----------------------------------------------------------------------- |
| 6 | + |
| 7 | +using System; |
| 8 | +using System.Collections.Generic; |
| 9 | +using System.Globalization; |
| 10 | +using System.IO; |
| 11 | +using System.Linq; |
| 12 | +using System.Runtime.Loader; |
| 13 | + |
| 14 | +using Microsoft.Build.Evaluation; |
| 15 | +using Microsoft.VisualStudio.TestTools.UnitTesting; |
| 16 | + |
| 17 | +using Ubiquity.NET.Versioning; |
| 18 | + |
| 19 | +namespace Ubiquity.Versioning.Build.Tasks.UT |
| 20 | +{ |
| 21 | + [TestClass] |
| 22 | + [TestCategory("Build Task Assembly")] |
| 23 | + public class AssemblyValidationTests |
| 24 | + { |
| 25 | + public AssemblyValidationTests( TestContext ctx ) |
| 26 | + { |
| 27 | + ArgumentNullException.ThrowIfNull(ctx); |
| 28 | + ArgumentException.ThrowIfNullOrWhiteSpace( ctx.TestResultsDirectory ); |
| 29 | + |
| 30 | + Context = ctx; |
| 31 | + } |
| 32 | + |
| 33 | + public TestContext Context { get; } |
| 34 | + |
| 35 | + // Repo assembly versions are defined via PowerShell (Or hard coded in the project file |
| 36 | + // for IDE builds) as the build task is not usable for itself. This verifies the build |
| 37 | + // versioning used in the scripts matches what is expected for an end-consumer by testing |
| 38 | + // the assemblies versioning information against the output from THAT task assembly. This |
| 39 | + // should be identical for IDE builds AND command line builds. Basically this verifies the |
| 40 | + // manual IDE properties as well as the automated build scripting matches what the task |
| 41 | + // itself produces. If anything is out of whack, this will complain. |
| 42 | + [TestMethod] |
| 43 | + [DataRow("netstandard2.0")] |
| 44 | + [DataRow("net48")] |
| 45 | + [DataRow("net8.0")] |
| 46 | + public void ValidateRepoAssemblyVersion( string targetFramework) |
| 47 | + { |
| 48 | + var globalProperties = new Dictionary<string,string> |
| 49 | + { |
| 50 | + ["BuildVersionXml"] = Path.Combine(Context.GetRepoRoot(), "BuildVersion.xml"), |
| 51 | + }; |
| 52 | + |
| 53 | + // For a CI build load the ciBuildIndex and ciBuildName from the generatedversion.props file |
| 54 | + // so the test knows what to expect. This does NOT verify the behavior of the tasks exactly unfortunately. |
| 55 | + // There is a non-determinism in computing the index based on a time stamp in particular a single date/time |
| 56 | + // string is converted based on seconds since midnight today (in UTC) so if two different implementations |
| 57 | + // compute a value at a different time that varies by as much as 2 seconds, then they will produce different |
| 58 | + // results even if behaving correctly. The use of seconds since midnight today makes it non-deterministic... |
| 59 | + // Unfortunately that's the algorithm chosen, though since this is a major release (and a full rename that |
| 60 | + // is something to re-visit) Until, that is deterministic, use the generated CI info all up. Other tests will |
| 61 | + // need to validate the behavior of the task. |
| 62 | + var (ciBuildIndex, ciBuildName, buildTime) = TestUtils.GetGeneratedCiBuildInfo(); |
| 63 | + |
| 64 | + // Build name depends on context of the build (Local, PR, CI, Release) |
| 65 | + // and therefore is NOT hard-coded in the tests. |
| 66 | + if(!string.IsNullOrWhiteSpace(ciBuildName)) |
| 67 | + { |
| 68 | + globalProperties["CiBuildName"] = ciBuildName; |
| 69 | + } |
| 70 | + |
| 71 | + if(!string.IsNullOrWhiteSpace(buildTime)) |
| 72 | + { |
| 73 | + // NOT using exact parsing as that's 'flaky' at best and doesn't actually handle all ISO-8601 formats |
| 74 | + // Also, NOT using assumption of UTC as commit dates from repo are local time based. ToBuildIndex() will |
| 75 | + // convert to UTC so that the resulting index is still consistent. |
| 76 | + var parsedBuildTime = DateTime.Parse(buildTime, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); |
| 77 | + string indexFromLib = parsedBuildTime.ToBuildIndex(); |
| 78 | + Assert.AreEqual(indexFromLib, ciBuildIndex, "Index computed with versioning library should match the index computed by scripts"); |
| 79 | + |
| 80 | + globalProperties["BuildTime"] = buildTime; |
| 81 | + } |
| 82 | + |
| 83 | + using var collection = new ProjectCollection(globalProperties); |
| 84 | + |
| 85 | + var (buildResults, props) = Context.CreateTestProjectAndInvokeTestedPackage(targetFramework, collection); |
| 86 | + |
| 87 | + string? taskAssembly = buildResults.Creator.ProjectInstance.GetOptionalProperty("_Ubiquity_NET_Versioning_Build_Tasks"); |
| 88 | + Assert.IsNotNull( taskAssembly, "Task assembly property should contain full path to the task DLL (Not NULL)" ); |
| 89 | + Context.WriteLine( $"Task Assembly: '{taskAssembly}'" ); |
| 90 | + |
| 91 | + Assert.IsFalse( string.IsNullOrWhiteSpace( taskAssembly ), "Task assembly property should contain full path to the task DLL (Not Whitespace)" ); |
| 92 | + Assert.IsNotNull( props.FileVersion, "Generated properties should have a 'FileVersion'" ); |
| 93 | + Context.WriteLine( $"Generated FileVersion: {props.FileVersion}" ); |
| 94 | + |
| 95 | + var alc = new AssemblyLoadContext("TestALC", isCollectible: true); |
| 96 | + try |
| 97 | + { |
| 98 | + var asm = alc.LoadFromAssemblyPath(taskAssembly); |
| 99 | + Assert.IsNotNull( asm, "should be able to load task assembly" ); |
| 100 | + var asmName = asm.GetName(); |
| 101 | + Version? asmVer = asmName.Version; |
| 102 | + Assert.IsNotNull( asmVer, "Task assembly should have a version" ); |
| 103 | + Context.WriteLine( $"TaskAssemblyVersion: {asmVer}" ); |
| 104 | + Context.WriteLine( $"AssemblyName: {asmName}" ); |
| 105 | + |
| 106 | + Assert.IsNotNull( props.FileVersionMajor, "Property value for Major should exist" ); |
| 107 | + Assert.AreEqual( (int)props.FileVersionMajor, asmVer.Major, "Major value of assembly version should match" ); |
| 108 | + |
| 109 | + Assert.IsNotNull( props.FileVersionMinor, "Property value for Minor should exist" ); |
| 110 | + Assert.AreEqual( (int)props.FileVersionMinor, asmVer.Minor, "Minor value of assembly version should match" ); |
| 111 | + |
| 112 | + Assert.IsNotNull( props.FileVersionBuild, "Property value for Build should exist" ); |
| 113 | + Assert.AreEqual( (int)props.FileVersionBuild, asmVer.Build, "Build value of assembly version should match" ); |
| 114 | + |
| 115 | + Assert.IsNotNull( props.FileVersionRevision, "Property value for Revision should exist" ); |
| 116 | + Assert.AreEqual( (int)props.FileVersionRevision, asmVer.Revision, "Revision value of assembly version should match" ); |
| 117 | + |
| 118 | + // Release builds won't have a CI component by definition so nothing to validate for those |
| 119 | + // Should get local, PR and CI builds before that to hit this case though. |
| 120 | + if(!string.IsNullOrWhiteSpace(ciBuildIndex)) |
| 121 | + { |
| 122 | + Assert.AreEqual( ciBuildIndex, props.CiBuildIndex, "BuildIndex computed in scripts should match computed value from task"); |
| 123 | + } |
| 124 | + |
| 125 | + // Test that AssemblyFileVersion on the task assembly matches expected value |
| 126 | + string fileVersion = ( from attr in asm.CustomAttributes |
| 127 | + where attr.AttributeType.FullName == "System.Reflection.AssemblyFileVersionAttribute" |
| 128 | + let val = attr.ConstructorArguments.Single().Value as string |
| 129 | + where val is not null |
| 130 | + select val |
| 131 | + ).Single(); |
| 132 | + |
| 133 | + Assert.AreEqual(props.FileVersion, fileVersion); |
| 134 | + |
| 135 | + // Test that AssemblyInformationalVersion on the task assembly matches expected value |
| 136 | + string informationalVersion = ( from attr in asm.CustomAttributes |
| 137 | + where attr.AttributeType.FullName == "System.Reflection.AssemblyInformationalVersionAttribute" |
| 138 | + let val = attr.ConstructorArguments.Single().Value as string |
| 139 | + where val is not null |
| 140 | + select val |
| 141 | + ).Single(); |
| 142 | + |
| 143 | + Assert.AreEqual(props.InformationalVersion, informationalVersion); |
| 144 | + } |
| 145 | + finally |
| 146 | + { |
| 147 | + alc.Unload(); |
| 148 | + } |
| 149 | + } |
| 150 | + } |
| 151 | +} |
0 commit comments