From 8812fe1127270937fa869370443afff7aeb862e3 Mon Sep 17 00:00:00 2001 From: Marty T <120425148+tippmar-nr@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:05:22 -0600 Subject: [PATCH] fix: Custom attribute values of type float and decimal were not serialized correctly. (#2975) --- .../Core/Attributes/AttributeDefinition.cs | 3 +- .../Attributes/AttributeTests.cs | 50 +++++++-------- .../CustomEvents/CustomEventWireModelTests.cs | 61 ++++++++++++++++++- .../AttributeComparer.cs | 17 +++--- 4 files changed, 94 insertions(+), 37 deletions(-) diff --git a/src/Agent/NewRelic/Agent/Core/Attributes/AttributeDefinition.cs b/src/Agent/NewRelic/Agent/Core/Attributes/AttributeDefinition.cs index 1842f1ff6c..d6a81d6226 100644 --- a/src/Agent/NewRelic/Agent/Core/Attributes/AttributeDefinition.cs +++ b/src/Agent/NewRelic/Agent/Core/Attributes/AttributeDefinition.cs @@ -121,9 +121,10 @@ public static AttributeDefinitionBuilder CreateCustomAttribute(s case TypeCode.Int32: return Convert.ToInt64(input); + // don't convert Decimal and Single to double. + // The value ends up getting serialized as a string, so there's no need for the conversion case TypeCode.Decimal: case TypeCode.Single: - return Convert.ToDouble(input); case TypeCode.Double: case TypeCode.Int64: diff --git a/tests/Agent/UnitTests/Core.UnitTest/Attributes/AttributeTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Attributes/AttributeTests.cs index 30eb57af36..c76043413e 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Attributes/AttributeTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Attributes/AttributeTests.cs @@ -6,7 +6,6 @@ using System; using System.Linq; using System.Collections.Generic; -using NUnit.Framework.Internal; namespace NewRelic.Agent.Core.Attributes.Tests { @@ -25,38 +24,17 @@ public class AttributeTests [TestCase((ulong)8, 8L)] [TestCase((float)1.0, 1D)] [TestCase(/*(double)*/2.0, 2D)] + [TestCase(0.2D, 0.2D)] + [TestCase(0.2F, 0.2F)] public void Attributes_with_valid_type_are_valid_attributes(object attributeValue, object expectedResult) { TestValue(attributeValue, expectedResult); } - - private void TestValue(object attributeValue, object expectedResult) - { - var filter = new AttributeFilter(new AttributeFilter.Settings()); - - var attribDef = AttributeDefinitionBuilder - .CreateCustomAttribute("test", AttributeDestinations.All) - .Build(filter); - - var attribVals = new AttributeValueCollection(AttributeValueCollection.AllTargetModelTypes); - - attribDef.TrySetValue(attribVals, attributeValue); - - var actualAttribVal = attribVals.GetAttributeValues(AttributeClassification.UserAttributes) - .FirstOrDefault(x => x.AttributeDefinition == attribDef); - - - NrAssert.Multiple( - () => Assert.That(actualAttribVal, Is.Not.Null), - () => Assert.That(actualAttribVal.Value, Is.EqualTo(expectedResult)) - ); - } - [Test] public void Attributes_with_decimal_type_are_valid_attributes() { - TestValue(1.0m, 1D); + TestValue(0.2M, 0.2M); } [Test] @@ -261,5 +239,27 @@ public void Attributes_value_size_other_strings() () => Assert.That(values.ElementAt(2).Value.ToString(), Has.Length.EqualTo(255)) ); } + + private void TestValue(object attributeValue, object expectedResult) + { + var filter = new AttributeFilter(new AttributeFilter.Settings()); + + var attribDef = AttributeDefinitionBuilder + .CreateCustomAttribute("test", AttributeDestinations.All) + .Build(filter); + + var attribVals = new AttributeValueCollection(AttributeValueCollection.AllTargetModelTypes); + + attribDef.TrySetValue(attribVals, attributeValue); + + var actualAttribVal = attribVals.GetAttributeValues(AttributeClassification.UserAttributes) + .FirstOrDefault(x => x.AttributeDefinition == attribDef); + + + NrAssert.Multiple( + () => Assert.That(actualAttribVal, Is.Not.Null), + () => Assert.That(actualAttribVal.Value, Is.EqualTo(expectedResult)) + ); + } } } diff --git a/tests/Agent/UnitTests/Core.UnitTest/CustomEvents/CustomEventWireModelTests.cs b/tests/Agent/UnitTests/Core.UnitTest/CustomEvents/CustomEventWireModelTests.cs index 68416ee456..686f2831ee 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/CustomEvents/CustomEventWireModelTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/CustomEvents/CustomEventWireModelTests.cs @@ -39,7 +39,7 @@ public void CustomEvents_MultipleEvents_Serialization() var customEvents = new CustomEventWireModel[countEvents]; var expectedSerializations = new List[]>(); - for(var i = 0; i < countEvents; i++) + for (var i = 0; i < countEvents; i++) { var timestampVal = DateTime.UtcNow; var typeVal = $"CustomEvent{i}"; @@ -87,7 +87,64 @@ public void CustomEvents_MultipleEvents_Serialization() AttributeComparer.CompareDictionaries(expectedSerializations[0], deserialized[0]); AttributeComparer.CompareDictionaries(expectedSerializations[1], deserialized[1]); } - } + [Test] + public void CustomEvents_UserAttributes_AllAttributeTypesSerializeCorrectly() + { + var dateTime = DateTime.UtcNow; + var timestampVal = dateTime; + var typeVal = $"CustomEvent"; + + var guid = Guid.NewGuid(); + var expectedSerialization = new Dictionary[] + { + new Dictionary() + { + {_attribDefs.Timestamp.Name, timestampVal.ToUnixTimeMilliseconds() }, + {_attribDefs.CustomEventType.Name, typeVal } + }, + + new Dictionary() + { + { "boolVal", true}, + { "dateTimeVal", dateTime}, + { "decimalVal", 0.2M}, + { "doubleVal", 0.2D}, + { "enumVal", AttributeDestinations.CustomEvent}, + { "floatVal", 0.2f}, + { "guidVal", guid}, + { "intVal", 2}, + { "longVal", 2L}, + { "stringVal", "string"}, + }, + + new Dictionary() + }; + + var attribVals = new AttributeValueCollection(AttributeDestinations.CustomEvent); + + _attribDefs.Timestamp.TrySetValue(attribVals, timestampVal); + _attribDefs.CustomEventType.TrySetValue(attribVals, typeVal); + + _attribDefs.GetCustomAttributeForCustomEvent("boolVal").TrySetValue(attribVals, true); + _attribDefs.GetCustomAttributeForCustomEvent("dateTimeVal").TrySetValue(attribVals, dateTime); + _attribDefs.GetCustomAttributeForCustomEvent("decimalVal").TrySetValue(attribVals, 0.2M); + _attribDefs.GetCustomAttributeForCustomEvent("doubleVal").TrySetValue(attribVals, 0.2D); + _attribDefs.GetCustomAttributeForCustomEvent("enumVal").TrySetValue(attribVals, AttributeDestinations.CustomEvent); + _attribDefs.GetCustomAttributeForCustomEvent("floatVal").TrySetValue(attribVals, 0.2f); + _attribDefs.GetCustomAttributeForCustomEvent("guidVal").TrySetValue(attribVals, guid); + _attribDefs.GetCustomAttributeForCustomEvent("intVal").TrySetValue(attribVals, 2); + _attribDefs.GetCustomAttributeForCustomEvent("longVal").TrySetValue(attribVals, 2L); + _attribDefs.GetCustomAttributeForCustomEvent("stringVal").TrySetValue(attribVals, "string"); + + var customEvent = new CustomEventWireModel(.5f, attribVals); + + var serialized = JsonConvert.SerializeObject(customEvent); + var expectedSerialized = JsonConvert.SerializeObject(expectedSerialization); + + Assert.That(serialized, Is.Not.Null); + Assert.That(serialized, Is.EqualTo(expectedSerialized)); + } + } } diff --git a/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/AttributeComparer.cs b/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/AttributeComparer.cs index f10c9a5bd2..6d5d959c0f 100644 --- a/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/AttributeComparer.cs +++ b/tests/Agent/UnitTests/NewRelic.Agent.TestUtilities/AttributeComparer.cs @@ -78,7 +78,7 @@ public static bool IsEqualTo(this object val1, object val2) case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: - return Math.Abs((double)val1Comp - (double)val2Comp) < .0001; + return Math.Abs(Convert.ToDouble(val1Comp) - Convert.ToDouble(val2Comp)) < .0001; // close enough, I guess? default: return val1Comp.Equals(val2Comp); @@ -111,25 +111,24 @@ public static object ConvertForCompare(object val) { case TypeCode.SByte: case TypeCode.Byte: - case TypeCode.Int16: case TypeCode.UInt16: - case TypeCode.Int32: case TypeCode.UInt32: - case TypeCode.Int64: case TypeCode.UInt64: + case TypeCode.Int16: + case TypeCode.Int32: return Convert.ToInt64(val); + case TypeCode.Decimal: case TypeCode.Single: case TypeCode.Double: - case TypeCode.Decimal: - return Convert.ToDouble(val); + case TypeCode.Int64: + case TypeCode.Boolean: + case TypeCode.String: + return val; case TypeCode.DateTime: return ((DateTime)val).ToString("o"); - case TypeCode.String: - return val; - default: return val.ToString(); }