diff --git a/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs b/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs new file mode 100644 index 000000000..a7977cf00 --- /dev/null +++ b/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs @@ -0,0 +1,112 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.IO; +using Serilog.Core; +using Serilog.Debugging; +using Serilog.Events; +using Serilog.Parameters; +using Serilog.Parsing; +using Serilog.Tests.Support; +using Xunit; + +namespace Serilog.Tests.Core +{ + public class LogEventPropertyCapturingTests + { + [Fact] + public void CapturingANamedScalarStringWorks() + { + Assert.Equal( + new[] { new LogEventProperty("who", new ScalarValue("world")) }, + Capture("Hello {who}", "world"), + new LogEventPropertyStructuralEqualityComparer()); + } + + [Fact] + public void CapturingAPositionalScalarStringWorks() + { + Assert.Equal( + new[] { new LogEventProperty("0", new ScalarValue("world")) }, + Capture("Hello {0}", "world"), + new LogEventPropertyStructuralEqualityComparer()); + } + + [Fact] + public void CapturingMixedPositionalAndNamedScalarsWorksUsingNames() + { + Assert.Equal(new[] + { + new LogEventProperty("who", new ScalarValue("worldNamed")), + new LogEventProperty("0", new ScalarValue("worldPositional")), + }, + Capture("Hello {who} {0} {0}", "worldNamed", "worldPositional"), + new LogEventPropertyStructuralEqualityComparer()); + } + + [Fact] + public void CapturingIntArrayAsScalarSequenceValuesWorks() + { + const string template = "Hello {sequence}"; + var templateArguments = new[] { 1, 2, 3, }; + var expected = new[] { + new LogEventProperty("sequence", + new SequenceValue(new[] { + new ScalarValue(1), + new ScalarValue(2), + new ScalarValue(3) })) }; + + Assert.Equal(expected, Capture(template, templateArguments), + new LogEventPropertyStructuralEqualityComparer()); + } + + [Fact] + public void CapturingDestructuredStringArrayAsScalarSequenceValuesWorks() + { + const string template = "Hello {@sequence}"; + var templateArguments = new[] { "1", "2", "3", }; + var expected = new[] { + new LogEventProperty("sequence", + new SequenceValue(new[] { + new ScalarValue("1"), + new ScalarValue("2"), + new ScalarValue("3") })) }; + + Assert.Equal(expected, Capture(template, new object[] { templateArguments }), + new LogEventPropertyStructuralEqualityComparer()); + } + + [Fact] + public void WillCaptureProvidedPositionalValuesEvenIfSomeAreMissing() + { + Assert.Equal(new[] + { + new LogEventProperty("0", new ScalarValue(0)), + new LogEventProperty("1", new ScalarValue(1)), + }, + Capture("Hello {3} {2} {1} {0} nothing more", 0, 1), // missing {2} and {3} + new LogEventPropertyStructuralEqualityComparer()); + } + + [Fact] + public void WillCaptureProvidedNamedValuesEvenIfSomeAreMissing() + { + Assert.Equal(new[] + { + new LogEventProperty("who", new ScalarValue("who")), + new LogEventProperty("what", new ScalarValue("what")), + }, + Capture("Hello {who} {what} {where}", "who", "what"), // missing "where" + new LogEventPropertyStructuralEqualityComparer()); + } + + static IEnumerable Capture(string messageTemplate, params object[] properties) + { + var mt = new MessageTemplateParser().Parse(messageTemplate); + var binder = new PropertyBinder( + new PropertyValueConverter(10, Enumerable.Empty(), Enumerable.Empty())); + return binder.ConstructProperties(mt, properties); + } + + } +} diff --git a/test/Serilog.Tests/Support/LogEventPropertyStructuralEqualityComparer.cs b/test/Serilog.Tests/Support/LogEventPropertyStructuralEqualityComparer.cs new file mode 100644 index 000000000..e1162ed8f --- /dev/null +++ b/test/Serilog.Tests/Support/LogEventPropertyStructuralEqualityComparer.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using Serilog.Events; + +namespace Serilog.Tests.Support +{ + class LogEventPropertyStructuralEqualityComparer : IEqualityComparer + { + readonly IEqualityComparer _valueEqualityComparer; + + public LogEventPropertyStructuralEqualityComparer( + IEqualityComparer valueEqualityComparer = null) + { + this._valueEqualityComparer = + valueEqualityComparer ?? new LogEventPropertyValueComparer(EqualityComparer.Default); + } + + public bool Equals(LogEventProperty x, LogEventProperty y) + { + if (x == null || y == null) + return false; // throw new Exception($"the comparer doesn't support nulls, x={x}, y={y}"); + + return x.Name == y.Name + && _valueEqualityComparer.Equals(x.Value, y.Value); + } + + public int GetHashCode(LogEventProperty obj) + { + return 0; + } + } +} diff --git a/test/Serilog.Tests/Support/LogEventPropertyStructuralEqualityComparerTests.cs b/test/Serilog.Tests/Support/LogEventPropertyStructuralEqualityComparerTests.cs new file mode 100644 index 000000000..7d79b4520 --- /dev/null +++ b/test/Serilog.Tests/Support/LogEventPropertyStructuralEqualityComparerTests.cs @@ -0,0 +1,89 @@ +using System; +using Serilog.Events; +using Xunit; + +namespace Serilog.Tests.Support +{ + public class LogEventPropertyStructuralEqualityComparerTests + { + [Fact] + public void HandlesNullAsNotEqual() + { + var sut = new LogEventPropertyStructuralEqualityComparer(); + Assert.False(sut.Equals(null, new LogEventProperty("a", new ScalarValue(null)))); + Assert.False(sut.Equals(new LogEventProperty("a", new ScalarValue(null)), null)); + Assert.False(sut.Equals(null, null)); + } + + [Fact] + public void LogEventPropertyStructuralEqualityComparerWorksForSequences() + { + var intStringDoubleScalarArray = + new[] { new ScalarValue(1), new ScalarValue("2"), new ScalarValue(3.0), }; + + var intStringFloatScalarArray = + new[] { new ScalarValue("1"), new ScalarValue(2), new ScalarValue(3.0f), }; + + var sequenceOfScalarsIntStringDoubleA = new LogEventProperty("a", new SequenceValue(intStringDoubleScalarArray)); + + var sequenceOfScalarsIntStringDoubleAStructurallyEqual = new LogEventProperty("a", + new SequenceValue(new[] { new ScalarValue(1), new ScalarValue("2"), new ScalarValue(3.0), })); + + var sequenceOfScalarsIntStringDoubleAStructurallyNotEqual = new LogEventProperty("a", + new SequenceValue(new [] { new ScalarValue(1), new ScalarValue("2"), new ScalarValue(3.1), })); + + var sequenceOfScalarsIntStringFloatA = new LogEventProperty("a", new ScalarValue(intStringFloatScalarArray)); + + var sequenceOfScalarsIntStringDoubleB = new LogEventProperty("b", new SequenceValue(intStringDoubleScalarArray)); + + var sut = new LogEventPropertyStructuralEqualityComparer(); + + // Structurally equal + Assert.True(sut.Equals(sequenceOfScalarsIntStringDoubleA, sequenceOfScalarsIntStringDoubleAStructurallyEqual)); + + // Not equal due to having a different property name (but otherwise structurally equal) + Assert.False(sut.Equals(sequenceOfScalarsIntStringDoubleA, sequenceOfScalarsIntStringDoubleB)); + + // Structurally not equal because element 3 has a different value + Assert.False(sut.Equals(sequenceOfScalarsIntStringDoubleA, sequenceOfScalarsIntStringDoubleAStructurallyNotEqual)); + + // Strucrtually not equal because element 3 has a different underlying value and type + Assert.False(sut.Equals(sequenceOfScalarsIntStringDoubleA, sequenceOfScalarsIntStringFloatA)); + } + + [Fact] + public void LogEventPropertyStructuralEqualityComparerWorksForScalars() + { + var scalarStringA = new LogEventProperty("a", new ScalarValue("a")); + var scalarStringAStructurallyEqual = new LogEventProperty("a", new ScalarValue("a")); + + var scalarStringB = new LogEventProperty("b", new ScalarValue("b")); + var scalarStringBStructurallyNotEqual = new LogEventProperty("b", new ScalarValue("notEqual")); + + var scalarIntA1 = new LogEventProperty("a", new ScalarValue(1)); + var scalarIntA1StructurallyEqual = new LogEventProperty("a", new ScalarValue(1)); + var scalarIntA1DiffValueSameType = new LogEventProperty("a", new ScalarValue(0)); + var scalarIntB1 = new LogEventProperty("b", new ScalarValue(1)); + + var guid1 = Guid.NewGuid(); + var guid2 = Guid.NewGuid(); + var scalarGuid1 = new LogEventProperty("1", new ScalarValue(guid1)); + var scalarGuid1StructurallyEqual = new LogEventProperty("1", new ScalarValue(guid1)); + var scalarGuid1StructurallyNotEqual = new LogEventProperty("1", new ScalarValue("notEqual")); + var scalarGuid2 = new LogEventProperty("2", new ScalarValue(guid2)); + + var sut = new LogEventPropertyStructuralEqualityComparer(); + + Assert.True(sut.Equals(scalarStringA, scalarStringAStructurallyEqual)); + Assert.True(sut.Equals(scalarIntA1, scalarIntA1StructurallyEqual)); + Assert.True(sut.Equals(scalarGuid1, scalarGuid1StructurallyEqual)); + + Assert.False(sut.Equals(scalarStringB, scalarStringBStructurallyNotEqual)); + Assert.False(sut.Equals(scalarIntA1, scalarIntB1)); + Assert.False(sut.Equals(scalarIntA1, scalarIntA1DiffValueSameType)); + + Assert.False(sut.Equals(scalarGuid1, scalarGuid2)); + Assert.False(sut.Equals(scalarGuid1, scalarGuid1StructurallyNotEqual)); + } + } +} diff --git a/test/Serilog.Tests/Support/LogEventPropertyValueComparer.cs b/test/Serilog.Tests/Support/LogEventPropertyValueComparer.cs new file mode 100644 index 000000000..232b1cf96 --- /dev/null +++ b/test/Serilog.Tests/Support/LogEventPropertyValueComparer.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Serilog.Events; + +namespace Serilog.Tests.Support +{ + class LogEventPropertyValueComparer : IEqualityComparer + { + readonly IEqualityComparer _objectEqualityComparer; + + public LogEventPropertyValueComparer(IEqualityComparer objectEqualityComparer = null) + { + this._objectEqualityComparer = objectEqualityComparer ?? EqualityComparer.Default; + } + + public bool Equals(LogEventPropertyValue x, LogEventPropertyValue y) + { + var scalarX = x as ScalarValue; + var scalarY = y as ScalarValue; + if (scalarX != null && scalarY != null) + { + return _objectEqualityComparer.Equals(scalarX.Value, scalarY.Value); + } + + var sequenceX = x as SequenceValue; + var sequenceY = y as SequenceValue; + if (sequenceX != null && sequenceY != null) + { + return sequenceX.Elements + .SequenceEqual(sequenceY.Elements, this); + } + + if (x is StructureValue || y is StructureValue) + { + throw new NotImplementedException(); + } + + if (x is DictionaryValue || y is DictionaryValue) + { + throw new NotImplementedException(); + } + + return false; + } + + public int GetHashCode(LogEventPropertyValue obj) + { + return 0; + } + } +}