diff --git a/eng/Versions.props b/eng/Versions.props index c8d6176ba64..1fa71c720eb 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -2,7 +2,7 @@ - 17.11.3release + 17.11.4release 17.10.4 15.1.0.0 preview diff --git a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs index 656f906ccfa..2470064694c 100644 --- a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs +++ b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs @@ -873,6 +873,7 @@ public void PropertyInitialValueEventArgs() e => e.HelpKeyword, e => e.SenderName); } + [Fact] public void ReadingCorruptedStreamThrows() { diff --git a/src/Framework.UnitTests/CustomEventArgSerialization_Tests.cs b/src/Framework.UnitTests/CustomEventArgSerialization_Tests.cs index 5e65f74a575..0da144267c5 100644 --- a/src/Framework.UnitTests/CustomEventArgSerialization_Tests.cs +++ b/src/Framework.UnitTests/CustomEventArgSerialization_Tests.cs @@ -974,5 +974,82 @@ private static void VerifyTaskFinished(TaskFinishedEventArgs genericEvent, TaskF newGenericEvent.TaskFile.ShouldBe(genericEvent.TaskFile, StringCompareShould.IgnoreCase); // "Expected TaskFile to Match" newGenericEvent.TaskName.ShouldBe(genericEvent.TaskName, StringCompareShould.IgnoreCase); // "Expected TaskName to Match" } + + + [Fact] + public void TestTelemetryEventArgs_AllProperties() + { + // Test using reasonable values + TelemetryEventArgs genericEvent = new TelemetryEventArgs { EventName = "Good", Properties = new Dictionary { { "Key", "Value" } } }; + genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + + TelemetryEventArgs newGenericEvent = RoundTrip(genericEvent); + + VerifyGenericEventArg(genericEvent, newGenericEvent); + VerifyTelemetryEvent(genericEvent, newGenericEvent); + } + + [Fact] + public void TestTelemetryEventArgs_NullProperties() + { + // Test using reasonable values + TelemetryEventArgs genericEvent = new TelemetryEventArgs { EventName = "Good", Properties = null }; + genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + + TelemetryEventArgs newGenericEvent = RoundTrip(genericEvent); + + // quirk - the properties dict is initialized to an empty dictionary by the default constructor, so it's not _really_ round-trippable. + // so we modify the source event for easier comparison here. + genericEvent.Properties = new Dictionary(); + + VerifyGenericEventArg(genericEvent, newGenericEvent); + VerifyTelemetryEvent(genericEvent, newGenericEvent); + } + + [Fact] + public void TestTelemetryEventArgs_NullEventName() + { + // Test using null event name + TelemetryEventArgs genericEvent = new TelemetryEventArgs { EventName = null, Properties = new Dictionary { { "Key", "Value" } } }; + genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + + TelemetryEventArgs newGenericEvent = RoundTrip(genericEvent); + + VerifyGenericEventArg(genericEvent, newGenericEvent); + VerifyTelemetryEvent(genericEvent, newGenericEvent); + } + + [Fact] + public void TestTelemetryEventArgs_NullPropertyValue() + { + // Test using null property value name + TelemetryEventArgs genericEvent = new TelemetryEventArgs { EventName = "Good", Properties = new Dictionary { { "Key", null } } }; + genericEvent.BuildEventContext = new BuildEventContext(5, 4, 3, 2); + + TelemetryEventArgs newGenericEvent = RoundTrip(genericEvent); + + VerifyGenericEventArg(genericEvent, newGenericEvent); + VerifyTelemetryEvent(genericEvent, newGenericEvent); + } + + private T RoundTrip(T original) + where T : BuildEventArgs, new() + { + _stream.Position = 0; + original.WriteToStream(_writer); + long streamWriteEndPosition = _stream.Position; + + _stream.Position = 0; + var actual = new T(); + actual.CreateFromStream(_reader, _eventArgVersion); + _stream.Position.ShouldBe(streamWriteEndPosition); // "Stream End Positions Should Match" + return actual; + } + + private static void VerifyTelemetryEvent(TelemetryEventArgs expected, TelemetryEventArgs actual) + { + actual.EventName.ShouldBe(expected.EventName); + actual.Properties.ShouldBe(expected.Properties); + } } } diff --git a/src/Framework/TelemetryEventArgs.cs b/src/Framework/TelemetryEventArgs.cs index 276ec0a1fa3..d3d57e9c5e5 100644 --- a/src/Framework/TelemetryEventArgs.cs +++ b/src/Framework/TelemetryEventArgs.cs @@ -6,8 +6,6 @@ using System.IO; using Microsoft.Build.Shared; -#nullable disable - namespace Microsoft.Build.Framework { /// @@ -19,12 +17,12 @@ public sealed class TelemetryEventArgs : BuildEventArgs /// /// Gets or sets the name of the event. /// - public string EventName { get; set; } + public string? EventName { get; set; } /// /// Gets or sets a list of properties associated with the event. /// - public IDictionary Properties { get; set; } = new Dictionary(); + public IDictionary Properties { get; set; } = new Dictionary(); internal override void WriteToStream(BinaryWriter writer) { @@ -34,13 +32,17 @@ internal override void WriteToStream(BinaryWriter writer) int count = Properties?.Count ?? 0; writer.Write7BitEncodedInt(count); + if (Properties == null) + { + return; + } + foreach (var kvp in Properties) { writer.Write(kvp.Key); - writer.Write(kvp.Value); + writer.WriteOptionalString(kvp.Value); } } - internal override void CreateFromStream(BinaryReader reader, int version) { base.CreateFromStream(reader, version); @@ -51,7 +53,7 @@ internal override void CreateFromStream(BinaryReader reader, int version) for (int i = 0; i < count; i++) { string key = reader.ReadString(); - string value = reader.ReadString(); + string? value = reader.ReadOptionalString(); Properties.Add(key, value); } }