From 817f1179d8ea626b6a121946f7752d89b51ae15d Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Sun, 16 Sep 2018 23:23:46 +0200 Subject: [PATCH 1/8] Report mismatch segment for strings of unequal lengths --- .../Primitives/StringEqualityValidator.cs | 22 +++++++++++++++++-- Tests/Shared.Specs/BasicEquivalencySpecs.cs | 2 +- Tests/Shared.Specs/StringAssertionSpecs.cs | 6 ++--- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Src/FluentAssertions/Primitives/StringEqualityValidator.cs b/Src/FluentAssertions/Primitives/StringEqualityValidator.cs index 5b749649e4..8f4f388c59 100644 --- a/Src/FluentAssertions/Primitives/StringEqualityValidator.cs +++ b/Src/FluentAssertions/Primitives/StringEqualityValidator.cs @@ -27,10 +27,28 @@ protected override bool ValidateAgainstSuperfluousWhitespace() protected override bool ValidateAgainstLengthDifferences() { + // Logic is a little bit convoluted because I want to avoid calculation + // of mismatch segment in case of equalLength == true for performance reason. + // If lazy version of FailWith would be introduced, calculation of mismatch + // segment can be moved directly to FailWith's argument + bool equalLength = subject.Length == expected.Length; + + string mismatchSegment = ""; + if (!equalLength) + { + int indexOfMismatch = subject.IndexOfFirstMismatch(expected, comparisonMode); + if(indexOfMismatch == -1) + { + indexOfMismatch = string.IsNullOrEmpty(subject) ? 0 : subject.Length - 1; + } + + mismatchSegment = subject.IndexedSegmentAt(indexOfMismatch); + } + return assertion - .ForCondition(subject.Length == expected.Length) + .ForCondition(equalLength) .FailWith( - ExpectationDescription + "{0} with a length of {1}{reason}, but {2} has a length of {3}.", + ExpectationDescription + "{0} with a length of {1}{reason}, but {2} has a length of {3}, differs near " + mismatchSegment + ".", expected, expected.Length, subject, subject.Length) .SourceSucceeded; } diff --git a/Tests/Shared.Specs/BasicEquivalencySpecs.cs b/Tests/Shared.Specs/BasicEquivalencySpecs.cs index 23965aaeef..06992c36ff 100644 --- a/Tests/Shared.Specs/BasicEquivalencySpecs.cs +++ b/Tests/Shared.Specs/BasicEquivalencySpecs.cs @@ -644,7 +644,7 @@ public void // Assert //----------------------------------------------------------------------------------------------------------- act.Should().Throw().WithMessage( - @"Expected member Member1 to be*""different"" with a length of 9, but*"""" has a length of 0.*"); + @"Expected member Member1 to be*""different"" with a length of 9, but*"""" has a length of 0*"); } [Fact] diff --git a/Tests/Shared.Specs/StringAssertionSpecs.cs b/Tests/Shared.Specs/StringAssertionSpecs.cs index f980fb63ba..bb5b491515 100644 --- a/Tests/Shared.Specs/StringAssertionSpecs.cs +++ b/Tests/Shared.Specs/StringAssertionSpecs.cs @@ -66,7 +66,7 @@ public void When_the_expected_string_is_shorter_than_the_actual_string_it_should // Assert //----------------------------------------------------------------------------------------------------------- act.Should().Throw().WithMessage( - "Expected string to be \"AB\" with a length of 2, but \"ABC\" has a length of 3."); + "Expected string to be \"AB\" with a length of 2, but \"ABC\" has a length of 3, differs near \"C\" (index 2)."); } [Fact] @@ -81,7 +81,7 @@ public void When_the_expected_string_is_longer_than_the_actual_string_it_should_ // Assert //----------------------------------------------------------------------------------------------------------- act.Should().Throw().WithMessage( - "Expected string to be \"ABC\" with a length of 3, but \"AB\" has a length of 2."); + "Expected string to be \"ABC\" with a length of 3, but \"AB\" has a length of 2, differs near \"B\" (index 1)."); } [Fact] @@ -1904,7 +1904,7 @@ public void When_non_empty_string_is_expected_to_be_equivalent_to_empty_it_shoul // Assert //----------------------------------------------------------------------------------------------------------- act.Should().Throw().WithMessage( - "Expected string to be equivalent to \"\" with a length of 0, but \"ABC\" has a length of 3."); + "Expected string to be equivalent to \"\" with a length of 0, but \"ABC\" has a length of 3, differs near \"ABC\" (index 0)."); } [Fact] From 2545fa20ea31959d1a8967e92a91c3e50f2b5f78 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Sun, 16 Sep 2018 23:51:53 +0200 Subject: [PATCH 2/8] Find correct mismatch index in case of one string being prefix of the other --- .../Primitives/StringEqualityValidator.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Src/FluentAssertions/Primitives/StringEqualityValidator.cs b/Src/FluentAssertions/Primitives/StringEqualityValidator.cs index 8f4f388c59..f02e7d6676 100644 --- a/Src/FluentAssertions/Primitives/StringEqualityValidator.cs +++ b/Src/FluentAssertions/Primitives/StringEqualityValidator.cs @@ -37,9 +37,22 @@ protected override bool ValidateAgainstLengthDifferences() if (!equalLength) { int indexOfMismatch = subject.IndexOfFirstMismatch(expected, comparisonMode); - if(indexOfMismatch == -1) + + // If there is no difference it means that subject and expected have common prefix + // and the first difference is after just that prefix. + if (indexOfMismatch == -1) { - indexOfMismatch = string.IsNullOrEmpty(subject) ? 0 : subject.Length - 1; + if(subject.Length < expected.Length) + { + // If subject is shorter, we point at its last character + indexOfMismatch = Math.Max(0, subject.Length-1); + } + else + { + // If subject is longer we point at first character after expected + indexOfMismatch = Math.Max(0, expected.Length); + } + } mismatchSegment = subject.IndexedSegmentAt(indexOfMismatch); From e63dff9907d261b61c7f983492a10c6dd475536e Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Mon, 17 Sep 2018 06:59:30 +0200 Subject: [PATCH 3/8] Add tests for validating equality of empty and not empty string --- Tests/Shared.Specs/StringAssertionSpecs.cs | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Tests/Shared.Specs/StringAssertionSpecs.cs b/Tests/Shared.Specs/StringAssertionSpecs.cs index bb5b491515..d4614fd095 100644 --- a/Tests/Shared.Specs/StringAssertionSpecs.cs +++ b/Tests/Shared.Specs/StringAssertionSpecs.cs @@ -84,6 +84,36 @@ public void When_the_expected_string_is_longer_than_the_actual_string_it_should_ "Expected string to be \"ABC\" with a length of 3, but \"AB\" has a length of 2, differs near \"B\" (index 1)."); } + [Fact] + public void When_the_expected_string_is_empty_it_should_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action act = () => "ABC".Should().Be(""); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw().WithMessage( + "Expected string to be \"\" with a length of 0, but \"ABC\" has a length of 3, differs near \"ABC\" (index 0)."); + } + + [Fact] + public void When_the_subject_string_is_empty_it_should_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action act = () => "".Should().Be("ABC"); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw().WithMessage( + "Expected string to be \"ABC\" with a length of 3, but \"\" has a length of 0, differs near \"\" (index 0)."); + } + [Fact] public void When_string_is_expected_to_equal_null_it_should_throw() { From c7748aba3be2c3d8ee747f31d2df8e34423efe9d Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Mon, 17 Sep 2018 20:17:55 +0200 Subject: [PATCH 4/8] Extract method for calculation of mismatch segment for strings of unequal lengths --- .../Primitives/StringEqualityValidator.cs | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/Src/FluentAssertions/Primitives/StringEqualityValidator.cs b/Src/FluentAssertions/Primitives/StringEqualityValidator.cs index f02e7d6676..be3a415486 100644 --- a/Src/FluentAssertions/Primitives/StringEqualityValidator.cs +++ b/Src/FluentAssertions/Primitives/StringEqualityValidator.cs @@ -33,30 +33,7 @@ protected override bool ValidateAgainstLengthDifferences() // segment can be moved directly to FailWith's argument bool equalLength = subject.Length == expected.Length; - string mismatchSegment = ""; - if (!equalLength) - { - int indexOfMismatch = subject.IndexOfFirstMismatch(expected, comparisonMode); - - // If there is no difference it means that subject and expected have common prefix - // and the first difference is after just that prefix. - if (indexOfMismatch == -1) - { - if(subject.Length < expected.Length) - { - // If subject is shorter, we point at its last character - indexOfMismatch = Math.Max(0, subject.Length-1); - } - else - { - // If subject is longer we point at first character after expected - indexOfMismatch = Math.Max(0, expected.Length); - } - - } - - mismatchSegment = subject.IndexedSegmentAt(indexOfMismatch); - } + string mismatchSegment = GetMismatchSegmentForStringsOfDifferentLengths(equalLength); return assertion .ForCondition(equalLength) @@ -66,6 +43,35 @@ protected override bool ValidateAgainstLengthDifferences() .SourceSucceeded; } + private string GetMismatchSegmentForStringsOfDifferentLengths(bool equalLength) + { + if (equalLength) + { + return ""; + } + + int indexOfMismatch = subject.IndexOfFirstMismatch(expected, comparisonMode); + + // If there is no difference it means that subject and expected have common prefix + // and the first difference is after just that prefix. + if (indexOfMismatch == -1) + { + if (subject.Length < expected.Length) + { + // If subject is shorter, we point at its last character + indexOfMismatch = Math.Max(0, subject.Length - 1); + } + else + { + // If subject is longer we point at first character after expected + indexOfMismatch = Math.Max(0, expected.Length); + } + } + + string mismatchSegment = subject.IndexedSegmentAt(indexOfMismatch); + return mismatchSegment; + } + protected override void ValidateAgainstMismatch() { int indexOfMismatch = subject.IndexOfFirstMismatch(expected, comparisonMode); From d14eca6dfa170d01a94379e1102e9ad4f85c15f1 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Mon, 17 Sep 2018 20:23:00 +0200 Subject: [PATCH 5/8] Adjust documentation with new format of assertion message for strings of unequal length --- docs/_pages/documentation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_pages/documentation.md b/docs/_pages/documentation.md index 40b6977e6d..60e46554d5 100644 --- a/docs/_pages/documentation.md +++ b/docs/_pages/documentation.md @@ -1290,7 +1290,7 @@ The above will batch the two failures, and throw an exception at the point of di E.g. Exception thrown at point of dispose contains: Expected value to be 10, but found 5. - Expected string to be "Expected" with a length of 8, but "Actual" has a length of 6. + Expected string to be "Expected" with a length of 8, but "Actual" has a length of 6, differs near "Act" (index 0). at........ From 928e283cea68054281acc6b7cab314ab3dd75116 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Mon, 17 Sep 2018 21:57:50 +0200 Subject: [PATCH 6/8] Readability fixes after review --- .../Primitives/StringEqualityValidator.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Src/FluentAssertions/Primitives/StringEqualityValidator.cs b/Src/FluentAssertions/Primitives/StringEqualityValidator.cs index be3a415486..4abe875fc2 100644 --- a/Src/FluentAssertions/Primitives/StringEqualityValidator.cs +++ b/Src/FluentAssertions/Primitives/StringEqualityValidator.cs @@ -52,24 +52,29 @@ private string GetMismatchSegmentForStringsOfDifferentLengths(bool equalLength) int indexOfMismatch = subject.IndexOfFirstMismatch(expected, comparisonMode); - // If there is no difference it means that subject and expected have common prefix - // and the first difference is after just that prefix. + // If there is no difference it means that either + // * subject starts with expected or + // * expected starts with subject if (indexOfMismatch == -1) { + // If subject is shorter we are sure that expected starts with subject if (subject.Length < expected.Length) { - // If subject is shorter, we point at its last character + // Subject is shorter so we point at its last character. + // We would like to point at next character as it is the real + // index of first mismatch, but we need to point at character existing in + // subject, so the next best thing is the last subject character. indexOfMismatch = Math.Max(0, subject.Length - 1); } else { - // If subject is longer we point at first character after expected + // If subject is longer we are sure that subject starts with expected + // and we point at first character after expected. indexOfMismatch = Math.Max(0, expected.Length); } } - string mismatchSegment = subject.IndexedSegmentAt(indexOfMismatch); - return mismatchSegment; + return subject.IndexedSegmentAt(indexOfMismatch); } protected override void ValidateAgainstMismatch() From 296dd6e22db95a68216eb95af7edc4513f75b11b Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Tue, 18 Sep 2018 21:37:04 +0200 Subject: [PATCH 7/8] Relax few string assertion test constrains regarding message --- Tests/Shared.Specs/StringAssertionSpecs.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Tests/Shared.Specs/StringAssertionSpecs.cs b/Tests/Shared.Specs/StringAssertionSpecs.cs index d4614fd095..7d56e4e959 100644 --- a/Tests/Shared.Specs/StringAssertionSpecs.cs +++ b/Tests/Shared.Specs/StringAssertionSpecs.cs @@ -65,8 +65,7 @@ public void When_the_expected_string_is_shorter_than_the_actual_string_it_should //----------------------------------------------------------------------------------------------------------- // Assert //----------------------------------------------------------------------------------------------------------- - act.Should().Throw().WithMessage( - "Expected string to be \"AB\" with a length of 2, but \"ABC\" has a length of 3, differs near \"C\" (index 2)."); + act.Should().Throw().WithMessage("*index 2*"); } [Fact] @@ -80,8 +79,7 @@ public void When_the_expected_string_is_longer_than_the_actual_string_it_should_ //----------------------------------------------------------------------------------------------------------- // Assert //----------------------------------------------------------------------------------------------------------- - act.Should().Throw().WithMessage( - "Expected string to be \"ABC\" with a length of 3, but \"AB\" has a length of 2, differs near \"B\" (index 1)."); + act.Should().Throw().WithMessage("*index 1*"); } [Fact] @@ -95,8 +93,7 @@ public void When_the_expected_string_is_empty_it_should_throw() //----------------------------------------------------------------------------------------------------------- // Assert //----------------------------------------------------------------------------------------------------------- - act.Should().Throw().WithMessage( - "Expected string to be \"\" with a length of 0, but \"ABC\" has a length of 3, differs near \"ABC\" (index 0)."); + act.Should().Throw().WithMessage("*index 0*"); } [Fact] @@ -110,8 +107,7 @@ public void When_the_subject_string_is_empty_it_should_throw() //----------------------------------------------------------------------------------------------------------- // Assert //----------------------------------------------------------------------------------------------------------- - act.Should().Throw().WithMessage( - "Expected string to be \"ABC\" with a length of 3, but \"\" has a length of 0, differs near \"\" (index 0)."); + act.Should().Throw().WithMessage("*index 0*"); } [Fact] From b3063e8b4802982d5a8b71abef42ad50a6873e56 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Wed, 19 Sep 2018 21:56:35 +0200 Subject: [PATCH 8/8] Prune unnecessary Math.Max, length is always positive anyway --- Src/FluentAssertions/Primitives/StringEqualityValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/FluentAssertions/Primitives/StringEqualityValidator.cs b/Src/FluentAssertions/Primitives/StringEqualityValidator.cs index 4abe875fc2..9bfc29ebc3 100644 --- a/Src/FluentAssertions/Primitives/StringEqualityValidator.cs +++ b/Src/FluentAssertions/Primitives/StringEqualityValidator.cs @@ -70,7 +70,7 @@ private string GetMismatchSegmentForStringsOfDifferentLengths(bool equalLength) { // If subject is longer we are sure that subject starts with expected // and we point at first character after expected. - indexOfMismatch = Math.Max(0, expected.Length); + indexOfMismatch = expected.Length; } }