From 91c00f34bfbc0d9902bbfd3ce10a112b5c757333 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Sun, 5 Dec 2021 23:14:49 +0800 Subject: [PATCH 1/9] refactor and tidy should --- .../Annotations/JetBrains.Annotations.cs | 48 ++ .../Annotations/ReSharperAttributes.cs | 70 -- .../AssertComparer.cs | 4 +- .../BooleanExtensions.cs | 28 + .../ComparableExtensions.cs | 101 +++ .../ComparableComparer.cs | 2 +- .../ComparerFactory.cs | 5 +- .../ComparisionResult.cs | 2 +- .../DefaultComparer.cs | 2 +- .../EnumerableComparer.cs | 2 +- .../EquatableComparer.cs | 2 +- .../GenericTypeComparer.cs | 2 +- .../IComparerStrategy.cs | 2 +- .../NoResult.cs | 2 +- .../ObjectExtension.cs | 2 +- .../TypeComparer.cs | 2 +- .../TypeExtension.cs | 2 +- .../DateTimeExtensions.cs | 28 + .../EnumerableExtensions.cs | 200 +++++ .../ExceptionExtensions.cs | 38 + .../FloatingPointExtensions.cs | 49 ++ .../Machine.Specifications.Should.csproj | 17 +- .../ObjectExtensions.cs | 270 ++++++ .../ShouldExtensionMethods.cs | 804 ------------------ .../SpecificationException.cs | 4 - .../StringExtensions.cs | 168 ++++ .../Internal/PrettyPrintingExtensions.cs | 2 +- .../Utility/ObjectGraphHelper.cs | 2 +- 28 files changed, 964 insertions(+), 896 deletions(-) create mode 100644 src/Machine.Specifications.Should/Annotations/JetBrains.Annotations.cs delete mode 100644 src/Machine.Specifications.Should/Annotations/ReSharperAttributes.cs create mode 100644 src/Machine.Specifications.Should/BooleanExtensions.cs create mode 100644 src/Machine.Specifications.Should/ComparableExtensions.cs rename src/Machine.Specifications.Should/{ComparerStrategies => Comparers}/ComparableComparer.cs (92%) rename src/Machine.Specifications.Should/{ComparerStrategies => Comparers}/ComparerFactory.cs (82%) rename src/Machine.Specifications.Should/{ComparerStrategies => Comparers}/ComparisionResult.cs (85%) rename src/Machine.Specifications.Should/{ComparerStrategies => Comparers}/DefaultComparer.cs (84%) rename src/Machine.Specifications.Should/{ComparerStrategies => Comparers}/EnumerableComparer.cs (95%) rename src/Machine.Specifications.Should/{ComparerStrategies => Comparers}/EquatableComparer.cs (87%) rename src/Machine.Specifications.Should/{ComparerStrategies => Comparers}/GenericTypeComparer.cs (92%) rename src/Machine.Specifications.Should/{ComparerStrategies => Comparers}/IComparerStrategy.cs (66%) rename src/Machine.Specifications.Should/{ComparerStrategies => Comparers}/NoResult.cs (72%) rename src/Machine.Specifications.Should/{ComparerStrategies => Comparers}/ObjectExtension.cs (76%) rename src/Machine.Specifications.Should/{ComparerStrategies => Comparers}/TypeComparer.cs (84%) rename src/Machine.Specifications.Should/{ComparerStrategies => Comparers}/TypeExtension.cs (82%) create mode 100644 src/Machine.Specifications.Should/DateTimeExtensions.cs create mode 100644 src/Machine.Specifications.Should/EnumerableExtensions.cs create mode 100644 src/Machine.Specifications.Should/ExceptionExtensions.cs create mode 100644 src/Machine.Specifications.Should/FloatingPointExtensions.cs create mode 100644 src/Machine.Specifications.Should/ObjectExtensions.cs create mode 100644 src/Machine.Specifications.Should/StringExtensions.cs diff --git a/src/Machine.Specifications.Should/Annotations/JetBrains.Annotations.cs b/src/Machine.Specifications.Should/Annotations/JetBrains.Annotations.cs new file mode 100644 index 00000000..bc6c2561 --- /dev/null +++ b/src/Machine.Specifications.Should/Annotations/JetBrains.Annotations.cs @@ -0,0 +1,48 @@ +using System; +using System.Diagnostics; + +namespace JetBrains.Annotations +{ + /// + /// Indicates that the marked method is assertion method, i.e. it halts the control flow if + /// one of the conditions is satisfied. To set the condition, mark one of the parameters with + /// attribute. + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class AssertionMethodAttribute : Attribute + { + } + + /// + /// Indicates the condition parameter of the assertion method. The method itself should be + /// marked by attribute. The mandatory argument of + /// the attribute is the assertion type. + /// + [AttributeUsage(AttributeTargets.Parameter)] + [Conditional("JETBRAINS_ANNOTATIONS")] + internal sealed class AssertionConditionAttribute : Attribute + { + public AssertionConditionAttribute(AssertionConditionType conditionType) + { + ConditionType = conditionType; + } + + public AssertionConditionType ConditionType { get; } + } + + /// + /// Specifies assertion type. If the assertion method argument satisfies the condition, + /// then the execution continues. Otherwise, execution is assumed to be halted. + /// + internal enum AssertionConditionType + { + /// Marked parameter should be evaluated to true. + IS_TRUE = 0, + /// Marked parameter should be evaluated to false. + IS_FALSE = 1, + /// Marked parameter should be evaluated to null value. + IS_NULL = 2, + /// Marked parameter should be evaluated to not null value. + IS_NOT_NULL = 3, + } +} diff --git a/src/Machine.Specifications.Should/Annotations/ReSharperAttributes.cs b/src/Machine.Specifications.Should/Annotations/ReSharperAttributes.cs deleted file mode 100644 index 76d2509e..00000000 --- a/src/Machine.Specifications.Should/Annotations/ReSharperAttributes.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; - -namespace Machine.Specifications.Annotations -{ - /// - /// Indicates the condition parameter of the assertion method. - /// The method itself should be marked by attribute. - /// The mandatory argument of the attribute is the assertion type. - /// - /// - [AttributeUsage(AttributeTargets.Parameter)] - public sealed class AssertionConditionAttribute : Attribute - { - private readonly AssertionConditionType myConditionType; - - /// - /// Initializes new instance of AssertionConditionAttribute - /// - /// Specifies condition type - public AssertionConditionAttribute(AssertionConditionType conditionType) - { - myConditionType = conditionType; - } - - /// - /// Gets condition type - /// - public AssertionConditionType ConditionType - { - get { return myConditionType; } - } - } - - /// - /// Indicates that the marked method is assertion method, i.e. it halts control flow if one of the conditions is satisfied. - /// To set the condition, mark one of the parameters with attribute - /// - /// - [AttributeUsage(AttributeTargets.Method)] - public sealed class AssertionMethodAttribute : Attribute - { - } - - /// - /// Specifies assertion type. If the assertion method argument satisifes the condition, then the execution continues. - /// Otherwise, execution is assumed to be halted - /// - public enum AssertionConditionType - { - /// - /// Indicates that the marked parameter should be evaluated to true - /// - IS_TRUE = 0, - - /// - /// Indicates that the marked parameter should be evaluated to false - /// - IS_FALSE = 1, - - /// - /// Indicates that the marked parameter should be evaluated to null value - /// - IS_NULL = 2, - - /// - /// Indicates that the marked parameter should be evaluated to not null value - /// - IS_NOT_NULL = 3, - } -} diff --git a/src/Machine.Specifications.Should/AssertComparer.cs b/src/Machine.Specifications.Should/AssertComparer.cs index 72200f0f..0a4256e3 100644 --- a/src/Machine.Specifications.Should/AssertComparer.cs +++ b/src/Machine.Specifications.Should/AssertComparer.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; -using Machine.Specifications.ComparerStrategies; +using Machine.Specifications.Comparers; namespace Machine.Specifications { - public class AssertComparer : IComparer, IEqualityComparer + internal class AssertComparer : IComparer, IEqualityComparer { public int Compare(T x, T y) { diff --git a/src/Machine.Specifications.Should/BooleanExtensions.cs b/src/Machine.Specifications.Should/BooleanExtensions.cs new file mode 100644 index 00000000..64ca2d20 --- /dev/null +++ b/src/Machine.Specifications.Should/BooleanExtensions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; +using JetBrains.Annotations; + +namespace Machine.Specifications +{ + internal static class BooleanExtensions + { + [AssertionMethod] + public static void ShouldBeFalse([AssertionCondition(AssertionConditionType.IS_FALSE)] this bool condition) + { + if (condition) + { + throw new SpecificationException("Should be [false] but is [true]"); + } + } + + [AssertionMethod] + public static void ShouldBeTrue([AssertionCondition(AssertionConditionType.IS_TRUE)] this bool condition) + { + if (!condition) + { + throw new SpecificationException("Should be [true] but is [false]"); + } + } + } +} diff --git a/src/Machine.Specifications.Should/ComparableExtensions.cs b/src/Machine.Specifications.Should/ComparableExtensions.cs new file mode 100644 index 00000000..355e78aa --- /dev/null +++ b/src/Machine.Specifications.Should/ComparableExtensions.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Machine.Specifications +{ + internal static class ComparableExtensions + { + public static IComparable ShouldBeGreaterThan(this IComparable arg1, IComparable arg2) + { + if (arg2 == null) + { + throw new ArgumentNullException(nameof(arg2)); + } + + if (arg1 == null) + { + throw NewException("Should be greater than {0} but is [null]", arg2); + } + + if (arg1.CompareTo(arg2.TryToChangeType(arg1.GetType())) <= 0) + { + throw NewException("Should be greater than {0} but is {1}", arg2, arg1); + } + + return arg1; + } + + public static IComparable ShouldBeGreaterThanOrEqualTo(this IComparable arg1, IComparable arg2) + { + if (arg2 == null) + { + throw new ArgumentNullException(nameof(arg2)); + } + + if (arg1 == null) + { + throw NewException("Should be greater than or equal to {0} but is [null]", arg2); + } + + if (arg1.CompareTo(arg2.TryToChangeType(arg1.GetType())) < 0) + { + throw NewException("Should be greater than or equal to {0} but is {1}", arg2, arg1); + } + + return arg1; + } + + public static IComparable ShouldBeLessThan(this IComparable arg1, IComparable arg2) + { + if (arg2 == null) + { + throw new ArgumentNullException(nameof(arg2)); + } + + if (arg1 == null) + { + throw NewException("Should be less than {0} but is [null]", arg2); + } + + if (arg1.CompareTo(arg2.TryToChangeType(arg1.GetType())) >= 0) + { + throw NewException("Should be less than {0} but is {1}", arg2, arg1); + } + + return arg1; + } + + public static IComparable ShouldBeLessThanOrEqualTo(this IComparable arg1, IComparable arg2) + { + if (arg2 == null) + { + throw new ArgumentNullException(nameof(arg2)); + } + + if (arg1 == null) + { + throw NewException("Should be less than or equal to {0} but is [null]", arg2); + } + + if (arg1.CompareTo(arg2.TryToChangeType(arg1.GetType())) > 0) + { + throw NewException("Should be less than or equal to {0} but is {1}", arg2, arg1); + } + + return arg1; + } + + private static object TryToChangeType(this object original, Type type) + { + try + { + return Convert.ChangeType(original, type); + } + catch + { + return original; + } + } + } +} diff --git a/src/Machine.Specifications.Should/ComparerStrategies/ComparableComparer.cs b/src/Machine.Specifications.Should/Comparers/ComparableComparer.cs similarity index 92% rename from src/Machine.Specifications.Should/ComparerStrategies/ComparableComparer.cs rename to src/Machine.Specifications.Should/Comparers/ComparableComparer.cs index 360cca98..9ac58e19 100644 --- a/src/Machine.Specifications.Should/ComparerStrategies/ComparableComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/ComparableComparer.cs @@ -1,6 +1,6 @@ using System; -namespace Machine.Specifications.ComparerStrategies +namespace Machine.Specifications.Comparers { class ComparableComparer : IComparerStrategy { diff --git a/src/Machine.Specifications.Should/ComparerStrategies/ComparerFactory.cs b/src/Machine.Specifications.Should/Comparers/ComparerFactory.cs similarity index 82% rename from src/Machine.Specifications.Should/ComparerStrategies/ComparerFactory.cs rename to src/Machine.Specifications.Should/Comparers/ComparerFactory.cs index 963d4506..5f212328 100644 --- a/src/Machine.Specifications.Should/ComparerStrategies/ComparerFactory.cs +++ b/src/Machine.Specifications.Should/Comparers/ComparerFactory.cs @@ -1,6 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Collections.Generic; -namespace Machine.Specifications.ComparerStrategies +namespace Machine.Specifications.Comparers { internal static class ComparerFactory { diff --git a/src/Machine.Specifications.Should/ComparerStrategies/ComparisionResult.cs b/src/Machine.Specifications.Should/Comparers/ComparisionResult.cs similarity index 85% rename from src/Machine.Specifications.Should/ComparerStrategies/ComparisionResult.cs rename to src/Machine.Specifications.Should/Comparers/ComparisionResult.cs index cac55f12..8634ac0b 100644 --- a/src/Machine.Specifications.Should/ComparerStrategies/ComparisionResult.cs +++ b/src/Machine.Specifications.Should/Comparers/ComparisionResult.cs @@ -1,4 +1,4 @@ -namespace Machine.Specifications.ComparerStrategies +namespace Machine.Specifications.Comparers { internal class ComparisionResult { diff --git a/src/Machine.Specifications.Should/ComparerStrategies/DefaultComparer.cs b/src/Machine.Specifications.Should/Comparers/DefaultComparer.cs similarity index 84% rename from src/Machine.Specifications.Should/ComparerStrategies/DefaultComparer.cs rename to src/Machine.Specifications.Should/Comparers/DefaultComparer.cs index ec0ba94c..5cc36cdc 100644 --- a/src/Machine.Specifications.Should/ComparerStrategies/DefaultComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/DefaultComparer.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Machine.Specifications.ComparerStrategies +namespace Machine.Specifications.Comparers { internal class DefaultComparer : IComparer { diff --git a/src/Machine.Specifications.Should/ComparerStrategies/EnumerableComparer.cs b/src/Machine.Specifications.Should/Comparers/EnumerableComparer.cs similarity index 95% rename from src/Machine.Specifications.Should/ComparerStrategies/EnumerableComparer.cs rename to src/Machine.Specifications.Should/Comparers/EnumerableComparer.cs index 67f30a69..3afa430a 100644 --- a/src/Machine.Specifications.Should/ComparerStrategies/EnumerableComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/EnumerableComparer.cs @@ -1,6 +1,6 @@ using System.Collections; -namespace Machine.Specifications.ComparerStrategies +namespace Machine.Specifications.Comparers { internal class EnumerableComparer : IComparerStrategy { diff --git a/src/Machine.Specifications.Should/ComparerStrategies/EquatableComparer.cs b/src/Machine.Specifications.Should/Comparers/EquatableComparer.cs similarity index 87% rename from src/Machine.Specifications.Should/ComparerStrategies/EquatableComparer.cs rename to src/Machine.Specifications.Should/Comparers/EquatableComparer.cs index 8eb333f2..3068cc45 100644 --- a/src/Machine.Specifications.Should/ComparerStrategies/EquatableComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/EquatableComparer.cs @@ -1,6 +1,6 @@ using System; -namespace Machine.Specifications.ComparerStrategies +namespace Machine.Specifications.Comparers { internal class EquatableComparer : IComparerStrategy { diff --git a/src/Machine.Specifications.Should/ComparerStrategies/GenericTypeComparer.cs b/src/Machine.Specifications.Should/Comparers/GenericTypeComparer.cs similarity index 92% rename from src/Machine.Specifications.Should/ComparerStrategies/GenericTypeComparer.cs rename to src/Machine.Specifications.Should/Comparers/GenericTypeComparer.cs index a79ee6fe..fc3acefa 100644 --- a/src/Machine.Specifications.Should/ComparerStrategies/GenericTypeComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/GenericTypeComparer.cs @@ -1,6 +1,6 @@ using System.Reflection; -namespace Machine.Specifications.ComparerStrategies +namespace Machine.Specifications.Comparers { internal class GenericTypeComparer : IComparerStrategy { diff --git a/src/Machine.Specifications.Should/ComparerStrategies/IComparerStrategy.cs b/src/Machine.Specifications.Should/Comparers/IComparerStrategy.cs similarity index 66% rename from src/Machine.Specifications.Should/ComparerStrategies/IComparerStrategy.cs rename to src/Machine.Specifications.Should/Comparers/IComparerStrategy.cs index 9576432c..79f8757c 100644 --- a/src/Machine.Specifications.Should/ComparerStrategies/IComparerStrategy.cs +++ b/src/Machine.Specifications.Should/Comparers/IComparerStrategy.cs @@ -1,4 +1,4 @@ -namespace Machine.Specifications.ComparerStrategies +namespace Machine.Specifications.Comparers { internal interface IComparerStrategy { diff --git a/src/Machine.Specifications.Should/ComparerStrategies/NoResult.cs b/src/Machine.Specifications.Should/Comparers/NoResult.cs similarity index 72% rename from src/Machine.Specifications.Should/ComparerStrategies/NoResult.cs rename to src/Machine.Specifications.Should/Comparers/NoResult.cs index 94349088..0abe64af 100644 --- a/src/Machine.Specifications.Should/ComparerStrategies/NoResult.cs +++ b/src/Machine.Specifications.Should/Comparers/NoResult.cs @@ -1,4 +1,4 @@ -namespace Machine.Specifications.ComparerStrategies +namespace Machine.Specifications.Comparers { internal class NoResult : ComparisionResult { diff --git a/src/Machine.Specifications.Should/ComparerStrategies/ObjectExtension.cs b/src/Machine.Specifications.Should/Comparers/ObjectExtension.cs similarity index 76% rename from src/Machine.Specifications.Should/ComparerStrategies/ObjectExtension.cs rename to src/Machine.Specifications.Should/Comparers/ObjectExtension.cs index 3a2063e4..f630d2fa 100644 --- a/src/Machine.Specifications.Should/ComparerStrategies/ObjectExtension.cs +++ b/src/Machine.Specifications.Should/Comparers/ObjectExtension.cs @@ -1,4 +1,4 @@ -namespace Machine.Specifications.ComparerStrategies +namespace Machine.Specifications.Comparers { internal static class ObjectExtension { diff --git a/src/Machine.Specifications.Should/ComparerStrategies/TypeComparer.cs b/src/Machine.Specifications.Should/Comparers/TypeComparer.cs similarity index 84% rename from src/Machine.Specifications.Should/ComparerStrategies/TypeComparer.cs rename to src/Machine.Specifications.Should/Comparers/TypeComparer.cs index 9743a031..f1ff4d8a 100644 --- a/src/Machine.Specifications.Should/ComparerStrategies/TypeComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/TypeComparer.cs @@ -1,4 +1,4 @@ -namespace Machine.Specifications.ComparerStrategies +namespace Machine.Specifications.Comparers { internal class TypeComparer : IComparerStrategy { diff --git a/src/Machine.Specifications.Should/ComparerStrategies/TypeExtension.cs b/src/Machine.Specifications.Should/Comparers/TypeExtension.cs similarity index 82% rename from src/Machine.Specifications.Should/ComparerStrategies/TypeExtension.cs rename to src/Machine.Specifications.Should/Comparers/TypeExtension.cs index 96334ef5..68881052 100644 --- a/src/Machine.Specifications.Should/ComparerStrategies/TypeExtension.cs +++ b/src/Machine.Specifications.Should/Comparers/TypeExtension.cs @@ -1,6 +1,6 @@ using System; -namespace Machine.Specifications.ComparerStrategies +namespace Machine.Specifications.Comparers { internal static class TypeExtension { diff --git a/src/Machine.Specifications.Should/DateTimeExtensions.cs b/src/Machine.Specifications.Should/DateTimeExtensions.cs new file mode 100644 index 00000000..a2b9d078 --- /dev/null +++ b/src/Machine.Specifications.Should/DateTimeExtensions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Machine.Specifications.Utility.Internal; + +namespace Machine.Specifications +{ + internal static class DateTimeExtensions + { + public static void ShouldBeCloseTo(this TimeSpan actual, TimeSpan expected, TimeSpan tolerance) + { + if (Math.Abs(actual.Ticks - expected.Ticks) > tolerance.Ticks) + { + throw new SpecificationException($"Should be within {tolerance.ToUsefulString()} of {expected.ToUsefulString()} but is {actual.ToUsefulString()}"); + } + } + + public static void ShouldBeCloseTo(this DateTime actual, DateTime expected, TimeSpan tolerance) + { + var difference = expected - actual; + + if (Math.Abs(difference.Ticks) > tolerance.Ticks) + { + throw new SpecificationException($"Should be within {tolerance.ToUsefulString()} of {expected.ToUsefulString()} but is {actual.ToUsefulString()}"); + } + } + } +} diff --git a/src/Machine.Specifications.Should/EnumerableExtensions.cs b/src/Machine.Specifications.Should/EnumerableExtensions.cs new file mode 100644 index 00000000..918db99d --- /dev/null +++ b/src/Machine.Specifications.Should/EnumerableExtensions.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using Machine.Specifications.Utility.Internal; + +namespace Machine.Specifications +{ + internal static class EnumerableExtensions + { + public static void ShouldEachConformTo(this IEnumerable list, Expression> condition) + { + var source = new List(list); + var func = condition.Compile(); + + var failingItems = source + .Where(x => func(x) == false) + .ToArray(); + + if (failingItems.Any()) + { + throw new SpecificationException(string.Format(@"Should contain only elements conforming to: {0}" + Environment.NewLine + "the following items did not meet the condition: {1}", + condition, + failingItems.EachToUsefulString())); + } + } + + public static void ShouldContain(this IEnumerable list, params object[] items) + { + var actualList = list.Cast(); + var expectedList = items.Cast(); + + actualList.ShouldContain(expectedList); + } + + public static void ShouldContain(this IEnumerable list, params T[] items) + { + list.ShouldContain((IEnumerable) items); + } + + public static void ShouldContain(this IEnumerable list, IEnumerable items) + { + var comparer = new AssertComparer(); + + var listArray = list.ToArray(); + var itemsArray = items.ToArray(); + + var noContain = itemsArray + .Where(x => !listArray.Contains(x, comparer)) + .ToList(); + + if (noContain.Any()) + { + throw new SpecificationException(string.Format( + @"Should contain: {0}" + Environment.NewLine + + "entire list: {1}" + Environment.NewLine + + "does not contain: {2}", + itemsArray.EachToUsefulString(), + listArray.EachToUsefulString(), + noContain.EachToUsefulString())); + } + } + + public static void ShouldContain(this IEnumerable list, Expression> condition) + { + var func = condition.Compile(); + var listArray = list.ToArray(); + + if (!listArray.Any(func)) + { + throw new SpecificationException(string.Format( + @"Should contain elements conforming to: {0}" + Environment.NewLine + + "entire list: {1}", + condition, + listArray.EachToUsefulString())); + } + } + + public static void ShouldNotContain(this IEnumerable list, params object[] items) + { + var actualList = list.Cast(); + var expectedList = items.Cast(); + + actualList.ShouldNotContain(expectedList); + } + + public static void ShouldNotContain(this IEnumerable list, params T[] items) + { + list.ShouldNotContain((IEnumerable) items); + } + + public static void ShouldNotContain(this IEnumerable list, IEnumerable items) + { + var comparer = new AssertComparer(); + + var listArray = list.ToArray(); + var itemsArray = items.ToArray(); + + var contains = itemsArray + .Where(x => listArray.Contains(x, comparer)) + .ToList(); + + if (contains.Any()) + { + throw new SpecificationException(string.Format( + @"Should not contain: {0}" + Environment.NewLine + + "entire list: {1}" + Environment.NewLine + + "does contain: {2}", + itemsArray.EachToUsefulString(), + listArray.EachToUsefulString(), + contains.EachToUsefulString())); + } + } + + public static void ShouldNotContain(this IEnumerable list, Expression> condition) + { + var func = condition.Compile(); + + var listArray = list.ToArray(); + var contains = listArray.Where(func).ToArray(); + + if (contains.Any()) + { + throw new SpecificationException(string.Format( + @"No elements should conform to: {0}" + Environment.NewLine + + "entire list: {1}" + Environment.NewLine + + "does contain: {2}", + condition, + listArray.EachToUsefulString(), + contains.EachToUsefulString())); + } + } + + public static void ShouldBeEmpty(this IEnumerable collection) + { + var items = collection.Cast().ToArray(); + + if (items.Any()) + { + throw NewException("Should be empty but contains:\n" + items.EachToUsefulString()); + } + } + + public static void ShouldNotBeEmpty(this IEnumerable collection) + { + if (!collection.Cast().Any()) + { + throw NewException("Should not be empty but is"); + } + } + + public static void ShouldContainOnly(this IEnumerable list, params T[] items) + { + list.ShouldContainOnly((IEnumerable) items); + } + + public static void ShouldContainOnly(this IEnumerable list, IEnumerable items) + { + var listArray = list.ToArray(); + var itemsArray = items.ToArray(); + + var source = new List(listArray); + var noContain = new List(); + var comparer = new AssertComparer(); + + foreach (var item in itemsArray) + { + if (!source.Contains(item, comparer)) + { + noContain.Add(item); + } + else + { + source.Remove(item); + } + } + + if (noContain.Any() || source.Any()) + { + var message = string.Format(@"Should contain only: {0}" + Environment.NewLine + "entire list: {1}", + itemsArray.EachToUsefulString(), + listArray.EachToUsefulString()); + + if (noContain.Any()) + { + message += "\ndoes not contain: " + noContain.EachToUsefulString(); + } + + if (source.Any()) + { + message += "\ndoes contain but shouldn't: " + source.EachToUsefulString(); + } + + throw new SpecificationException(message); + } + } + } +} diff --git a/src/Machine.Specifications.Should/ExceptionExtensions.cs b/src/Machine.Specifications.Should/ExceptionExtensions.cs new file mode 100644 index 00000000..8297e484 --- /dev/null +++ b/src/Machine.Specifications.Should/ExceptionExtensions.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Machine.Specifications +{ + internal static class ExceptionExtensions + { + public static void ShouldContainErrorMessage(this Exception exception, string expected) + { + exception.Message.ShouldContain(expected); + } + + public static Exception ShouldBeThrownBy(this Type exceptionType, Action method) + { + var exception = CatchException(method); + + exception.ShouldNotBeNull(); + exception.ShouldBeAssignableTo(exceptionType); + + return exception; + } + + private static Exception CatchException(Action throwingAction) + { + try + { + throwingAction(); + } + catch (Exception ex) + { + return ex; + } + + return null; + } + } +} diff --git a/src/Machine.Specifications.Should/FloatingPointExtensions.cs b/src/Machine.Specifications.Should/FloatingPointExtensions.cs new file mode 100644 index 00000000..a55a49a9 --- /dev/null +++ b/src/Machine.Specifications.Should/FloatingPointExtensions.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Machine.Specifications.Utility.Internal; + +namespace Machine.Specifications +{ + internal static class FloatingPointExtensions + { + public static void ShouldBeCloseTo(this float actual, float expected) + { + ShouldBeCloseTo(actual, expected, 0.0000001f); + } + + public static void ShouldBeCloseTo(this float actual, float expected, float tolerance) + { + if (Math.Abs(actual - expected) > tolerance) + { + throw new SpecificationException($"Should be within {tolerance.ToUsefulString()} of {expected.ToUsefulString()} but is {actual.ToUsefulString()}"); + } + } + + public static void ShouldBeCloseTo(this double actual, double expected) + { + ShouldBeCloseTo(actual, expected, 0.0000001f); + } + + public static void ShouldBeCloseTo(this double actual, double expected, double tolerance) + { + if (Math.Abs(actual - expected) > tolerance) + { + throw new SpecificationException($"Should be within {tolerance.ToUsefulString()} of {expected.ToUsefulString()} but is {actual.ToUsefulString()}"); + } + } + + public static void ShouldBeCloseTo(this decimal actual, decimal expected) + { + ShouldBeCloseTo(actual, expected, 0.0000001m); + } + + public static void ShouldBeCloseTo(this decimal actual, decimal expected, decimal tolerance) + { + if (Math.Abs(actual - expected) > tolerance) + { + throw new SpecificationException($"Should be within {tolerance.ToUsefulString()} of {expected.ToUsefulString()} but is {actual.ToUsefulString()}"); + } + } + } +} diff --git a/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj b/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj index 7d19d8dc..65f32f06 100644 --- a/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj +++ b/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj @@ -1,7 +1,7 @@  - net472;netstandard2.0 + netstandard2.0 Machine.Specifications Assertion library for Machine.Specifications @@ -13,8 +13,23 @@ MIT + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/src/Machine.Specifications.Should/ObjectExtensions.cs b/src/Machine.Specifications.Should/ObjectExtensions.cs new file mode 100644 index 00000000..90561d15 --- /dev/null +++ b/src/Machine.Specifications.Should/ObjectExtensions.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using JetBrains.Annotations; +using Machine.Specifications.Utility; +using Machine.Specifications.Utility.Internal; + +namespace Machine.Specifications +{ + internal static class ObjectExtensions + { + [AssertionMethod] + public static void ShouldBeNull([AssertionCondition(AssertionConditionType.IS_NULL)] this object anObject) + { + if (anObject != null) + { + throw new SpecificationException($"Should be [null] but is {anObject.ToUsefulString()}"); + } + } + + [AssertionMethod] + public static void ShouldNotBeNull([AssertionCondition(AssertionConditionType.IS_NOT_NULL)] this object anObject) + { + if (anObject == null) + { + throw new SpecificationException("Should be [not null] but is [null]"); + } + } + + public static object ShouldBeTheSameAs(this object actual, object expected) + { + if (!ReferenceEquals(actual, expected)) + { + throw new SpecificationException($"Should be the same as {expected} but is {actual}"); + } + + return expected; + } + + public static object ShouldNotBeTheSameAs(this object actual, object expected) + { + if (ReferenceEquals(actual, expected)) + { + throw new SpecificationException($"Should not be the same as {expected} but is {actual}"); + } + + return expected; + } + + public static void ShouldBeOfExactType(this object actual, Type expected) + { + if (actual == null) + { + throw new SpecificationException($"Should be of type {expected} but is [null]"); + } + + if (actual.GetType() != expected) + { + throw new SpecificationException($"Should be of type {expected} but is of type {actual.GetType()}"); + } + } + + public static void ShouldNotBeOfExactType(this object actual, Type expected) + { + if (actual == null) + { + throw new SpecificationException($"Should not be of type {expected} but is [null]"); + } + + if (actual.GetType() == expected) + { + throw new SpecificationException($"Should not be of type {expected} but is of type {actual.GetType()}"); + } + } + + public static void ShouldBeOfExactType(this object actual) + { + actual.ShouldBeOfExactType(typeof(T)); + } + + public static void ShouldNotBeOfExactType(this object actual) + { + actual.ShouldNotBeOfExactType(typeof(T)); + } + + public static void ShouldBeAssignableTo(this object actual, Type expected) + { + if (actual == null) + { + throw new SpecificationException($"Should be assignable to type {expected} but is [null]"); + } + + if (!expected.IsInstanceOfType(actual)) + { + throw new SpecificationException($"Should be assignable to type {expected} but is not. Actual type is {actual.GetType()}"); + } + } + + public static void ShouldNotBeAssignableTo(this object actual, Type expected) + { + if (actual == null) + { + throw new SpecificationException($"Should not be assignable to type {expected} but is [null]"); + } + + if (expected.IsInstanceOfType(actual)) + { + throw new SpecificationException($"Should not be assignable to type {expected} but is. Actual type is {actual.GetType()}"); + } + } + + public static void ShouldBeAssignableTo(this object actual) + { + actual.ShouldBeAssignableTo(typeof(T)); + } + + public static void ShouldNotBeAssignableTo(this object actual) + { + actual.ShouldNotBeAssignableTo(typeof(T)); + } + + public static void ShouldBeLike(this object obj, object expected) + { + var exceptions = ShouldBeLikeInternal(obj, expected, string.Empty, new HashSet()).ToArray(); + + if (exceptions.Any()) + { + throw NewException(exceptions.Select(e => e.Message).Aggregate(string.Empty, (r, m) => r + m + Environment.NewLine + Environment.NewLine).TrimEnd()); + } + } + + private static IEnumerable ShouldBeLikeInternal(object obj, object expected, string nodeName, HashSet visited) + { + // Stop at already checked -pairs to prevent infinite loops (cycles in object graphs). Additionally + // this also avoids re-equality-evaluation for already compared pairs. + var objExpectedTuple = new ReferentialEqualityTuple(obj, expected); + + if (visited.Contains(objExpectedTuple)) + { + return Enumerable.Empty(); + } + + visited.Add(objExpectedTuple); + + ObjectGraphHelper.INode expectedNode = null; + + var nodeType = typeof(ObjectGraphHelper.LiteralNode); + + if (obj != null && expected != null) + { + expectedNode = ObjectGraphHelper.GetGraph(expected); + nodeType = expectedNode.GetType(); + } + + if (nodeType == typeof(ObjectGraphHelper.LiteralNode)) + { + try + { + obj.ShouldEqual(expected); + } + catch (SpecificationException ex) + { + return new[] { NewException($"{{0}}:{Environment.NewLine}{ex.Message}", nodeName) }; + } + + return Enumerable.Empty(); + } + + if (nodeType == typeof(ObjectGraphHelper.SequenceNode)) + { + if (obj == null) + { + var errorMessage = PrettyPrintingExtensions.FormatErrorMessage(null, expected); + + return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; + } + + var actualNode = ObjectGraphHelper.GetGraph(obj); + + if (actualNode.GetType() != typeof(ObjectGraphHelper.SequenceNode)) + { + var errorMessage = $" Expected: Array or Sequence{Environment.NewLine} But was: {obj.GetType()}"; + + return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; + } + + var expectedValues = ((ObjectGraphHelper.SequenceNode) expectedNode)?.ValueGetters.ToArray(); + var actualValues = ((ObjectGraphHelper.SequenceNode) actualNode).ValueGetters.ToArray(); + + var expectedCount = expectedValues?.Length ?? 0; + var actualCount = actualValues.Length; + + if (expectedCount != actualCount) + { + var errorMessage = string.Format(" Expected: Sequence length of {1}{0} But was: {2}", Environment.NewLine, expectedCount, actualCount); + + return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; + } + + return Enumerable.Range(0, expectedCount) + .SelectMany(i => ShouldBeLikeInternal(actualValues.ElementAt(i)(), expectedValues?.ElementAt(i)(), $"{nodeName}[{i}]", visited)); + } + + if (nodeType == typeof(ObjectGraphHelper.KeyValueNode)) + { + var actualNode = ObjectGraphHelper.GetGraph(obj); + + if (actualNode.GetType() != typeof(ObjectGraphHelper.KeyValueNode)) + { + var errorMessage = $" Expected: Class{Environment.NewLine} But was: {obj?.GetType()}"; + + return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; + } + + var expectedKeyValues = ((ObjectGraphHelper.KeyValueNode) expectedNode)?.KeyValues; + var actualKeyValues = ((ObjectGraphHelper.KeyValueNode) actualNode).KeyValues; + + return expectedKeyValues?.SelectMany(kv => + { + var fullNodeName = string.IsNullOrEmpty(nodeName) + ? kv.Name + : $"{nodeName}.{kv.Name}"; + var actualKeyValue = actualKeyValues.SingleOrDefault(k => k.Name == kv.Name); + + if (actualKeyValue == null) + { + var errorMessage = string.Format(" Expected: {1}{0} But was: Not Defined", + Environment.NewLine, kv.ValueGetter().ToUsefulString()); + + return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", fullNodeName) }; + } + + return ShouldBeLikeInternal(actualKeyValue.ValueGetter(), kv.ValueGetter(), fullNodeName, visited); + }); + } + + throw new InvalidOperationException("Unknown node type"); + } + + private class ReferentialEqualityTuple + { + private readonly object obj; + + private readonly object expected; + + public ReferentialEqualityTuple(object obj, object expected) + { + this.obj = obj; + this.expected = expected; + } + + public override int GetHashCode() + { + return RuntimeHelpers.GetHashCode(obj) * RuntimeHelpers.GetHashCode(expected); + } + + public override bool Equals(object other) + { + if (!(other is ReferentialEqualityTuple otherSimpleTuple)) + { + return false; + } + + return ReferenceEquals(obj, otherSimpleTuple.obj) && ReferenceEquals(expected, otherSimpleTuple.expected); + } + } + } +} diff --git a/src/Machine.Specifications.Should/ShouldExtensionMethods.cs b/src/Machine.Specifications.Should/ShouldExtensionMethods.cs index ab92305c..8c0be8b2 100644 --- a/src/Machine.Specifications.Should/ShouldExtensionMethods.cs +++ b/src/Machine.Specifications.Should/ShouldExtensionMethods.cs @@ -1,13 +1,6 @@ using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Text.RegularExpressions; -using Machine.Specifications.Annotations; -using Machine.Specifications.Utility; using Machine.Specifications.Utility.Internal; namespace Machine.Specifications @@ -21,24 +14,6 @@ private static bool SafeEquals(this T left, T right) return comparer.Compare(left, right) == 0; } - [AssertionMethod] - public static void ShouldBeFalse([AssertionCondition(AssertionConditionType.IS_FALSE)] this bool condition) - { - if (condition) - { - throw new SpecificationException("Should be [false] but is [true]"); - } - } - - [AssertionMethod] - public static void ShouldBeTrue([AssertionCondition(AssertionConditionType.IS_TRUE)] this bool condition) - { - if (!condition) - { - throw new SpecificationException("Should be [true] but is [false]"); - } - } - public static T ShouldEqual(this T actual, T expected) { if (!actual.SafeEquals(expected)) @@ -59,116 +34,6 @@ public static object ShouldNotEqual(this T actual, T expected) return actual; } - [AssertionMethod] - public static void ShouldBeNull([AssertionCondition(AssertionConditionType.IS_NULL)] this object anObject) - { - if (anObject != null) - { - throw new SpecificationException($"Should be [null] but is {anObject.ToUsefulString()}"); - } - } - - [AssertionMethod] - public static void ShouldNotBeNull([AssertionCondition(AssertionConditionType.IS_NOT_NULL)] this object anObject) - { - if (anObject == null) - { - throw new SpecificationException("Should be [not null] but is [null]"); - } - } - - public static object ShouldBeTheSameAs(this object actual, object expected) - { - if (!ReferenceEquals(actual, expected)) - { - throw new SpecificationException($"Should be the same as {expected} but is {actual}"); - } - - return expected; - } - - public static object ShouldNotBeTheSameAs(this object actual, object expected) - { - if (ReferenceEquals(actual, expected)) - { - throw new SpecificationException($"Should not be the same as {expected} but is {actual}"); - } - - return expected; - } - - public static void ShouldBeOfExactType(this object actual, Type expected) - { - if (actual == null) - { - throw new SpecificationException($"Should be of type {expected} but is [null]"); - } - - if (actual.GetType() != expected) - { - throw new SpecificationException($"Should be of type {expected} but is of type {actual.GetType()}"); - } - } - - public static void ShouldNotBeOfExactType(this object actual, Type expected) - { - if (actual == null) - { - throw new SpecificationException($"Should not be of type {expected} but is [null]"); - } - - if (actual.GetType() == expected) - { - throw new SpecificationException($"Should not be of type {expected} but is of type {actual.GetType()}"); - } - } - - public static void ShouldBeOfExactType(this object actual) - { - actual.ShouldBeOfExactType(typeof(T)); - } - - public static void ShouldNotBeOfExactType(this object actual) - { - actual.ShouldNotBeOfExactType(typeof(T)); - } - - public static void ShouldBeAssignableTo(this object actual, Type expected) - { - if (actual == null) - { - throw new SpecificationException($"Should be assignable to type {expected} but is [null]"); - } - - if (!expected.IsInstanceOfType(actual)) - { - throw new SpecificationException($"Should be assignable to type {expected} but is not. Actual type is {actual.GetType()}"); - } - } - - public static void ShouldNotBeAssignableTo(this object actual, Type expected) - { - if (actual == null) - { - throw new SpecificationException($"Should not be assignable to type {expected} but is [null]"); - } - - if (expected.IsInstanceOfType(actual)) - { - throw new SpecificationException($"Should not be assignable to type {expected} but is. Actual type is {actual.GetType()}"); - } - } - - public static void ShouldBeAssignableTo(this object actual) - { - actual.ShouldBeAssignableTo(typeof(T)); - } - - public static void ShouldNotBeAssignableTo(this object actual) - { - actual.ShouldNotBeAssignableTo(typeof(T)); - } - public static void ShouldMatch(this T actual, Expression> condition) { var matches = condition.Compile().Invoke(actual); @@ -181,129 +46,6 @@ public static void ShouldMatch(this T actual, Expression> condi throw new SpecificationException($"Should match expression [{condition}], but does not."); } - public static void ShouldEachConformTo(this IEnumerable list, Expression> condition) - { - var source = new List(list); - var func = condition.Compile(); - - var failingItems = source - .Where(x => func(x) == false) - .ToArray(); - - if (failingItems.Any()) - { - throw new SpecificationException(string.Format(@"Should contain only elements conforming to: {0}" + Environment.NewLine + "the following items did not meet the condition: {1}", - condition, - failingItems.EachToUsefulString())); - } - } - - public static void ShouldContain(this IEnumerable list, params object[] items) - { - var actualList = list.Cast(); - var expectedList = items.Cast(); - - actualList.ShouldContain(expectedList); - } - - public static void ShouldContain(this IEnumerable list, params T[] items) - { - list.ShouldContain((IEnumerable)items); - } - - public static void ShouldContain(this IEnumerable list, IEnumerable items) - { - var comparer = new AssertComparer(); - - var listArray = list.ToArray(); - var itemsArray = items.ToArray(); - - var noContain = itemsArray - .Where(x => !listArray.Contains(x, comparer)) - .ToList(); - - if (noContain.Any()) - { - throw new SpecificationException(string.Format( - @"Should contain: {0}" + Environment.NewLine + - "entire list: {1}" + Environment.NewLine + - "does not contain: {2}", - itemsArray.EachToUsefulString(), - listArray.EachToUsefulString(), - noContain.EachToUsefulString())); - } - } - - public static void ShouldContain(this IEnumerable list, Expression> condition) - { - var func = condition.Compile(); - var listArray = list.ToArray(); - - if (!listArray.Any(func)) - { - throw new SpecificationException(string.Format( - @"Should contain elements conforming to: {0}" + Environment.NewLine + - "entire list: {1}", - condition, - listArray.EachToUsefulString())); - } - } - - public static void ShouldNotContain(this IEnumerable list, params object[] items) - { - var actualList = list.Cast(); - var expectedList = items.Cast(); - - actualList.ShouldNotContain(expectedList); - } - - public static void ShouldNotContain(this IEnumerable list, params T[] items) - { - list.ShouldNotContain((IEnumerable)items); - } - - public static void ShouldNotContain(this IEnumerable list, IEnumerable items) - { - var comparer = new AssertComparer(); - - var listArray = list.ToArray(); - var itemsArray = items.ToArray(); - - var contains = itemsArray - .Where(x => listArray.Contains(x, comparer)) - .ToList(); - - if (contains.Any()) - { - throw new SpecificationException(string.Format( - @"Should not contain: {0}" + Environment.NewLine + - "entire list: {1}" + Environment.NewLine + - "does contain: {2}", - itemsArray.EachToUsefulString(), - listArray.EachToUsefulString(), - contains.EachToUsefulString())); - } - } - - public static void ShouldNotContain(this IEnumerable list, Expression> condition) - { - var func = condition.Compile(); - - var listArray = list.ToArray(); - var contains = listArray.Where(func).ToArray(); - - if (contains.Any()) - { - throw new SpecificationException(string.Format( - @"No elements should conform to: {0}" + Environment.NewLine + - "entire list: {1}" + Environment.NewLine + - "does contain: {2}", - condition, - listArray.EachToUsefulString(), - contains.EachToUsefulString())); - } - } - private static SpecificationException NewException(string message, params object[] parameters) { if (parameters.Any()) @@ -313,551 +55,5 @@ private static SpecificationException NewException(string message, params object return new SpecificationException(message); } - - public static IComparable ShouldBeGreaterThan(this IComparable arg1, IComparable arg2) - { - if (arg2 == null) - { - throw new ArgumentNullException(nameof(arg2)); - } - - if (arg1 == null) - { - throw NewException("Should be greater than {0} but is [null]", arg2); - } - - if (arg1.CompareTo(arg2.TryToChangeType(arg1.GetType())) <= 0) - { - throw NewException("Should be greater than {0} but is {1}", arg2, arg1); - } - - return arg1; - } - - public static IComparable ShouldBeGreaterThanOrEqualTo(this IComparable arg1, IComparable arg2) - { - if (arg2 == null) - { - throw new ArgumentNullException(nameof(arg2)); - } - - if (arg1 == null) - { - throw NewException("Should be greater than or equal to {0} but is [null]", arg2); - } - - if (arg1.CompareTo(arg2.TryToChangeType(arg1.GetType())) < 0) - { - throw NewException("Should be greater than or equal to {0} but is {1}", arg2, arg1); - } - - return arg1; - } - - private static object TryToChangeType(this object original, Type type) - { - try - { - return Convert.ChangeType(original, type); - } - catch - { - return original; - } - } - - public static IComparable ShouldBeLessThan(this IComparable arg1, IComparable arg2) - { - if (arg2 == null) - { - throw new ArgumentNullException(nameof(arg2)); - } - - if (arg1 == null) - { - throw NewException("Should be less than {0} but is [null]", arg2); - } - - if (arg1.CompareTo(arg2.TryToChangeType(arg1.GetType())) >= 0) - { - throw NewException("Should be less than {0} but is {1}", arg2, arg1); - } - - return arg1; - } - - public static IComparable ShouldBeLessThanOrEqualTo(this IComparable arg1, IComparable arg2) - { - if (arg2 == null) - { - throw new ArgumentNullException(nameof(arg2)); - } - - if (arg1 == null) - { - throw NewException("Should be less than or equal to {0} but is [null]", arg2); - } - - if (arg1.CompareTo(arg2.TryToChangeType(arg1.GetType())) > 0) - { - throw NewException("Should be less than or equal to {0} but is {1}", arg2, arg1); - } - - return arg1; - } - - public static void ShouldBeCloseTo(this float actual, float expected) - { - ShouldBeCloseTo(actual, expected, 0.0000001f); - } - - public static void ShouldBeCloseTo(this float actual, float expected, float tolerance) - { - if (Math.Abs(actual - expected) > tolerance) - { - throw new SpecificationException($"Should be within {tolerance.ToUsefulString()} of {expected.ToUsefulString()} but is {actual.ToUsefulString()}"); - } - } - - public static void ShouldBeCloseTo(this double actual, double expected) - { - ShouldBeCloseTo(actual, expected, 0.0000001f); - } - - public static void ShouldBeCloseTo(this double actual, double expected, double tolerance) - { - if (Math.Abs(actual - expected) > tolerance) - { - throw new SpecificationException($"Should be within {tolerance.ToUsefulString()} of {expected.ToUsefulString()} but is {actual.ToUsefulString()}"); - } - } - - public static void ShouldBeCloseTo(this decimal actual, decimal expected) - { - ShouldBeCloseTo(actual, expected, 0.0000001m); - } - - public static void ShouldBeCloseTo(this decimal actual, decimal expected, decimal tolerance) - { - if (Math.Abs(actual - expected) > tolerance) - { - throw new SpecificationException($"Should be within {tolerance.ToUsefulString()} of {expected.ToUsefulString()} but is {actual.ToUsefulString()}"); - } - } - - public static void ShouldBeCloseTo(this TimeSpan actual, TimeSpan expected, TimeSpan tolerance) - { - if (Math.Abs(actual.Ticks - expected.Ticks) > tolerance.Ticks) - { - throw new SpecificationException($"Should be within {tolerance.ToUsefulString()} of {expected.ToUsefulString()} but is {actual.ToUsefulString()}"); - } - } - - public static void ShouldBeCloseTo(this DateTime actual, DateTime expected, TimeSpan tolerance) - { - var difference = expected - actual; - - if (Math.Abs(difference.Ticks) > tolerance.Ticks) - { - throw new SpecificationException($"Should be within {tolerance.ToUsefulString()} of {expected.ToUsefulString()} but is {actual.ToUsefulString()}"); - } - } - - public static void ShouldBeEmpty(this IEnumerable collection) - { - var items = collection.Cast().ToArray(); - - if (items.Any()) - { - throw NewException("Should be empty but contains:\n" + items.EachToUsefulString()); - } - } - - public static void ShouldBeEmpty(this string aString) - { - if (aString == null) - { - throw new SpecificationException("Should be empty but is [null]"); - } - - if (!string.IsNullOrEmpty(aString)) - { - throw NewException("Should be empty but is {0}", aString); - } - } - - public static void ShouldNotBeEmpty(this IEnumerable collection) - { - if (!collection.Cast().Any()) - { - throw NewException("Should not be empty but is"); - } - } - - public static void ShouldNotBeEmpty(this string aString) - { - if (string.IsNullOrEmpty(aString)) - { - throw NewException("Should not be empty but is"); - } - } - - public static void ShouldMatch(this string actual, string pattern) - { - if (pattern == null) - { - throw new ArgumentNullException(nameof(pattern)); - } - - if (actual == null) - { - throw NewException("Should match regex {0} but is [null]", pattern); - } - - ShouldMatch(actual, new Regex(pattern)); - } - - public static void ShouldMatch(this string actual, Regex pattern) - { - if (pattern == null) - { - throw new ArgumentNullException(nameof(pattern)); - } - - if (actual == null) - { - throw NewException("Should match regex {0} but is [null]", pattern); - } - - if (!pattern.IsMatch(actual)) - { - throw NewException("Should match {0} but is {1}", pattern, actual); - } - } - - public static void ShouldContain(this string actual, string expected) - { - if (expected == null) - { - throw new ArgumentNullException(nameof(expected)); - } - - if (actual == null) - { - throw NewException("Should contain {0} but is [null]", expected); - } - - if (!actual.Contains(expected)) - { - throw NewException("Should contain {0} but is {1}", expected, actual); - } - } - - public static void ShouldNotContain(this string actual, string notExpected) - { - if (notExpected == null) - { - throw new ArgumentNullException(nameof(notExpected)); - } - - if (actual == null) - { - return; - } - - if (actual.Contains(notExpected)) - { - throw NewException("Should not contain {0} but is {1}", notExpected, actual); - } - } - - public static string ShouldBeEqualIgnoringCase(this string actual, string expected) - { - if (expected == null) - { - throw new ArgumentNullException(nameof(expected)); - } - - if (actual == null) - { - throw NewException("Should be equal ignoring case to {0} but is [null]", expected); - } - - if (CultureInfo.InvariantCulture.CompareInfo.Compare(actual, expected, CompareOptions.IgnoreCase) != 0) - { - throw NewException("Should be equal ignoring case to {0} but is {1}", expected, actual); - } - - return actual; - } - - public static void ShouldStartWith(this string actual, string expected) - { - if (expected == null) - { - throw new ArgumentNullException(nameof(expected)); - } - - if (actual == null) - { - throw NewException("Should start with {0} but is [null]", expected); - } - - if (!actual.StartsWith(expected)) - { - throw NewException("Should start with {0} but is {1}", expected, actual); - } - } - - public static void ShouldEndWith(this string actual, string expected) - { - if (expected == null) - { - throw new ArgumentNullException(nameof(expected)); - } - - if (actual == null) - { - throw NewException("Should end with {0} but is [null]", expected); - } - - if (!actual.EndsWith(expected)) - { - throw NewException("Should end with {0} but is {1}", expected, actual); - } - } - - public static void ShouldBeSurroundedWith(this string actual, string expectedStartDelimiter, string expectedEndDelimiter) - { - actual.ShouldStartWith(expectedStartDelimiter); - actual.ShouldEndWith(expectedEndDelimiter); - } - - public static void ShouldBeSurroundedWith(this string actual, string expectedDelimiter) - { - actual.ShouldStartWith(expectedDelimiter); - actual.ShouldEndWith(expectedDelimiter); - } - - public static void ShouldContainErrorMessage(this Exception exception, string expected) - { - exception.Message.ShouldContain(expected); - } - - public static void ShouldContainOnly(this IEnumerable list, params T[] items) - { - list.ShouldContainOnly((IEnumerable)items); - } - - public static void ShouldContainOnly(this IEnumerable list, IEnumerable items) - { - var listArray = list.ToArray(); - var itemsArray = items.ToArray(); - - var source = new List(listArray); - var noContain = new List(); - var comparer = new AssertComparer(); - - foreach (var item in itemsArray) - { - if (!source.Contains(item, comparer)) - { - noContain.Add(item); - } - else - { - source.Remove(item); - } - } - - if (noContain.Any() || source.Any()) - { - var message = string.Format(@"Should contain only: {0}" + Environment.NewLine + "entire list: {1}", - itemsArray.EachToUsefulString(), - listArray.EachToUsefulString()); - - if (noContain.Any()) - { - message += "\ndoes not contain: " + noContain.EachToUsefulString(); - } - - if (source.Any()) - { - message += "\ndoes contain but shouldn't: " + source.EachToUsefulString(); - } - - throw new SpecificationException(message); - } - } - - public static Exception ShouldBeThrownBy(this Type exceptionType, Action method) - { - var exception = CatchException(method); - - ShouldNotBeNull(exception); - ShouldBeAssignableTo(exception, exceptionType); - - return exception; - } - - private static Exception CatchException(Action throwingAction) - { - try - { - throwingAction(); - } - catch (Exception ex) - { - return ex; - } - - return null; - } - - public static void ShouldBeLike(this object obj, object expected) - { - var exceptions = ShouldBeLikeInternal(obj, expected, string.Empty, new HashSet()).ToArray(); - - if (exceptions.Any()) - { - throw NewException(exceptions.Select(e => e.Message).Aggregate(string.Empty, (r, m) => r + m + Environment.NewLine + Environment.NewLine).TrimEnd()); - } - } - - private static IEnumerable ShouldBeLikeInternal(object obj, object expected, string nodeName, HashSet visited) - { - // Stop at already checked -pairs to prevent infinite loops (cycles in object graphs). Additionally - // this also avoids re-equality-evaluation for already compared pairs. - var objExpectedTuple = new ReferentialEqualityTuple(obj, expected); - - if (visited.Contains(objExpectedTuple)) - { - return Enumerable.Empty(); - } - - visited.Add(objExpectedTuple); - - ObjectGraphHelper.INode expectedNode = null; - - var nodeType = typeof(ObjectGraphHelper.LiteralNode); - - if (obj != null && expected != null) - { - expectedNode = ObjectGraphHelper.GetGraph(expected); - nodeType = expectedNode.GetType(); - } - - if (nodeType == typeof(ObjectGraphHelper.LiteralNode)) - { - try - { - obj.ShouldEqual(expected); - } - catch (SpecificationException ex) - { - return new[] { NewException($"{{0}}:{Environment.NewLine}{ex.Message}", nodeName) }; - } - - return Enumerable.Empty(); - } - - if (nodeType == typeof(ObjectGraphHelper.SequenceNode)) - { - if (obj == null) - { - var errorMessage = PrettyPrintingExtensions.FormatErrorMessage(null, expected); - - return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; - } - - var actualNode = ObjectGraphHelper.GetGraph(obj); - - if (actualNode.GetType() != typeof(ObjectGraphHelper.SequenceNode)) - { - var errorMessage = $" Expected: Array or Sequence{Environment.NewLine} But was: {obj.GetType()}"; - - return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; - } - - var expectedValues = ((ObjectGraphHelper.SequenceNode)expectedNode)?.ValueGetters.ToArray(); - var actualValues = ((ObjectGraphHelper.SequenceNode)actualNode).ValueGetters.ToArray(); - - var expectedCount = expectedValues?.Length ?? 0; - var actualCount = actualValues.Length; - - if (expectedCount != actualCount) - { - var errorMessage = string.Format(" Expected: Sequence length of {1}{0} But was: {2}", Environment.NewLine, expectedCount, actualCount); - - return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; - } - - return Enumerable.Range(0, expectedCount) - .SelectMany(i => ShouldBeLikeInternal(actualValues.ElementAt(i)(), expectedValues?.ElementAt(i)(), $"{nodeName}[{i}]", visited)); - } - - if (nodeType == typeof(ObjectGraphHelper.KeyValueNode)) - { - var actualNode = ObjectGraphHelper.GetGraph(obj); - - if (actualNode.GetType() != typeof(ObjectGraphHelper.KeyValueNode)) - { - var errorMessage = $" Expected: Class{Environment.NewLine} But was: {obj?.GetType()}"; - - return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; - } - - var expectedKeyValues = ((ObjectGraphHelper.KeyValueNode)expectedNode)?.KeyValues; - var actualKeyValues = ((ObjectGraphHelper.KeyValueNode)actualNode).KeyValues; - - return expectedKeyValues?.SelectMany(kv => - { - var fullNodeName = string.IsNullOrEmpty(nodeName) - ? kv.Name - : $"{nodeName}.{kv.Name}"; - var actualKeyValue = actualKeyValues.SingleOrDefault(k => k.Name == kv.Name); - - if (actualKeyValue == null) - { - var errorMessage = string.Format(" Expected: {1}{0} But was: Not Defined", - Environment.NewLine, kv.ValueGetter().ToUsefulString()); - - return new[] {NewException($"{{0}}:{Environment.NewLine}{errorMessage}", fullNodeName)}; - } - - return ShouldBeLikeInternal(actualKeyValue.ValueGetter(), kv.ValueGetter(), fullNodeName, visited); - }); - } - - throw new InvalidOperationException("Unknown node type"); - } - - private class ReferentialEqualityTuple - { - private readonly object obj; - - private readonly object expected; - - public ReferentialEqualityTuple(object obj, object expected) - { - this.obj = obj; - this.expected = expected; - } - - public override int GetHashCode() - { - return RuntimeHelpers.GetHashCode (obj) * RuntimeHelpers.GetHashCode (expected); - } - - public override bool Equals(object other) - { - if (!(other is ReferentialEqualityTuple otherSimpleTuple)) - { - return false; - } - - return ReferenceEquals(obj, otherSimpleTuple.obj) && ReferenceEquals(expected, otherSimpleTuple.expected); - } - } } } diff --git a/src/Machine.Specifications.Should/SpecificationException.cs b/src/Machine.Specifications.Should/SpecificationException.cs index 52fb85ca..069bfc93 100644 --- a/src/Machine.Specifications.Should/SpecificationException.cs +++ b/src/Machine.Specifications.Should/SpecificationException.cs @@ -3,9 +3,7 @@ namespace Machine.Specifications { -#if !NETSTANDARD [Serializable] -#endif public class SpecificationException : Exception { public SpecificationException() @@ -22,13 +20,11 @@ public SpecificationException(string message, Exception inner) { } -#if !NETSTANDARD protected SpecificationException( SerializationInfo info, StreamingContext context) : base(info, context) { } -#endif } } diff --git a/src/Machine.Specifications.Should/StringExtensions.cs b/src/Machine.Specifications.Should/StringExtensions.cs new file mode 100644 index 00000000..1433e19a --- /dev/null +++ b/src/Machine.Specifications.Should/StringExtensions.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Machine.Specifications +{ + internal static class StringExtensions + { + public static void ShouldBeEmpty(this string aString) + { + if (aString == null) + { + throw new SpecificationException("Should be empty but is [null]"); + } + + if (!string.IsNullOrEmpty(aString)) + { + throw NewException("Should be empty but is {0}", aString); + } + } + + public static void ShouldNotBeEmpty(this string aString) + { + if (string.IsNullOrEmpty(aString)) + { + throw NewException("Should not be empty but is"); + } + } + + public static void ShouldMatch(this string actual, string pattern) + { + if (pattern == null) + { + throw new ArgumentNullException(nameof(pattern)); + } + + if (actual == null) + { + throw NewException("Should match regex {0} but is [null]", pattern); + } + + ShouldMatch(actual, new Regex(pattern)); + } + + public static void ShouldMatch(this string actual, Regex pattern) + { + if (pattern == null) + { + throw new ArgumentNullException(nameof(pattern)); + } + + if (actual == null) + { + throw NewException("Should match regex {0} but is [null]", pattern); + } + + if (!pattern.IsMatch(actual)) + { + throw NewException("Should match {0} but is {1}", pattern, actual); + } + } + + public static void ShouldContain(this string actual, string expected) + { + if (expected == null) + { + throw new ArgumentNullException(nameof(expected)); + } + + if (actual == null) + { + throw NewException("Should contain {0} but is [null]", expected); + } + + if (!actual.Contains(expected)) + { + throw NewException("Should contain {0} but is {1}", expected, actual); + } + } + + public static void ShouldNotContain(this string actual, string notExpected) + { + if (notExpected == null) + { + throw new ArgumentNullException(nameof(notExpected)); + } + + if (actual == null) + { + return; + } + + if (actual.Contains(notExpected)) + { + throw NewException("Should not contain {0} but is {1}", notExpected, actual); + } + } + + public static string ShouldBeEqualIgnoringCase(this string actual, string expected) + { + if (expected == null) + { + throw new ArgumentNullException(nameof(expected)); + } + + if (actual == null) + { + throw NewException("Should be equal ignoring case to {0} but is [null]", expected); + } + + if (CultureInfo.InvariantCulture.CompareInfo.Compare(actual, expected, CompareOptions.IgnoreCase) != 0) + { + throw NewException("Should be equal ignoring case to {0} but is {1}", expected, actual); + } + + return actual; + } + + public static void ShouldStartWith(this string actual, string expected) + { + if (expected == null) + { + throw new ArgumentNullException(nameof(expected)); + } + + if (actual == null) + { + throw NewException("Should start with {0} but is [null]", expected); + } + + if (!actual.StartsWith(expected)) + { + throw NewException("Should start with {0} but is {1}", expected, actual); + } + } + + public static void ShouldEndWith(this string actual, string expected) + { + if (expected == null) + { + throw new ArgumentNullException(nameof(expected)); + } + + if (actual == null) + { + throw NewException("Should end with {0} but is [null]", expected); + } + + if (!actual.EndsWith(expected)) + { + throw NewException("Should end with {0} but is {1}", expected, actual); + } + } + + public static void ShouldBeSurroundedWith(this string actual, string expectedStartDelimiter, string expectedEndDelimiter) + { + actual.ShouldStartWith(expectedStartDelimiter); + actual.ShouldEndWith(expectedEndDelimiter); + } + + public static void ShouldBeSurroundedWith(this string actual, string expectedDelimiter) + { + actual.ShouldStartWith(expectedDelimiter); + actual.ShouldEndWith(expectedDelimiter); + } + } +} diff --git a/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs b/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs index 8785c1f2..7ebd0a59 100644 --- a/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs +++ b/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs @@ -8,7 +8,7 @@ namespace Machine.Specifications.Utility.Internal { - public static class PrettyPrintingExtensions + internal static class PrettyPrintingExtensions { private const string CurlyBraceSurround = "{{{0}}}"; diff --git a/src/Machine.Specifications.Should/Utility/ObjectGraphHelper.cs b/src/Machine.Specifications.Should/Utility/ObjectGraphHelper.cs index 6e03af70..88ae3fb9 100644 --- a/src/Machine.Specifications.Should/Utility/ObjectGraphHelper.cs +++ b/src/Machine.Specifications.Should/Utility/ObjectGraphHelper.cs @@ -9,7 +9,7 @@ namespace Machine.Specifications.Utility /// /// Modified from Steve Wagner's ObjectDiff prototype https://gist.github.com/2841822 /// - public static class ObjectGraphHelper + internal static class ObjectGraphHelper { public static INode GetGraph(object obj) { From 3bdcd408615eea1952b4397b8f7f4ca2ae61da9c Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Mon, 6 Dec 2021 16:45:23 +0800 Subject: [PATCH 2/9] refactoring --- .../ObjectGraphSpecs.cs} | 105 +++++++------- .../BooleanExtensions.cs | 7 +- .../ComparableExtensions.cs | 17 ++- .../DateTimeExtensions.cs | 6 +- .../EnumerableExtensions.cs | 14 +- .../ExceptionExtensions.cs | 4 +- .../FloatingPointExtensions.cs | 6 +- .../Machine.Specifications.Should.csproj | 4 - .../ObjectExtensions.cs | 54 +++++--- .../Reflection/INode.cs | 6 + .../Reflection/KeyValueNode.cs | 9 ++ .../Reflection/LiteralNode.cs | 7 + .../Reflection/Member.cs | 11 ++ .../Reflection/ObjectGraph.cs | 74 ++++++++++ .../Reflection/SequenceNode.cs | 10 ++ .../ShouldExtensionMethods.cs | 1 + .../StringExtensions.cs | 17 ++- .../Text/EnumerableExtensions.cs | 40 ++++++ .../Text/ObjectExtensions.cs | 58 ++++++++ .../Text/StringExtensions.cs | 44 ++++++ .../Internal/PrettyPrintingExtensions.cs | 131 +----------------- .../Utility/ObjectGraphHelper.cs | 101 -------------- 22 files changed, 400 insertions(+), 326 deletions(-) rename src/Machine.Specifications.Should.Specs/{Utility/ObjectGraphHelperSpecs.cs => Reflection/ObjectGraphSpecs.cs} (58%) create mode 100644 src/Machine.Specifications.Should/Reflection/INode.cs create mode 100644 src/Machine.Specifications.Should/Reflection/KeyValueNode.cs create mode 100644 src/Machine.Specifications.Should/Reflection/LiteralNode.cs create mode 100644 src/Machine.Specifications.Should/Reflection/Member.cs create mode 100644 src/Machine.Specifications.Should/Reflection/ObjectGraph.cs create mode 100644 src/Machine.Specifications.Should/Reflection/SequenceNode.cs create mode 100644 src/Machine.Specifications.Should/Text/EnumerableExtensions.cs create mode 100644 src/Machine.Specifications.Should/Text/ObjectExtensions.cs create mode 100644 src/Machine.Specifications.Should/Text/StringExtensions.cs delete mode 100644 src/Machine.Specifications.Should/Utility/ObjectGraphHelper.cs diff --git a/src/Machine.Specifications.Should.Specs/Utility/ObjectGraphHelperSpecs.cs b/src/Machine.Specifications.Should.Specs/Reflection/ObjectGraphSpecs.cs similarity index 58% rename from src/Machine.Specifications.Should.Specs/Utility/ObjectGraphHelperSpecs.cs rename to src/Machine.Specifications.Should.Specs/Reflection/ObjectGraphSpecs.cs index a2a342f1..e78407d3 100644 --- a/src/Machine.Specifications.Should.Specs/Utility/ObjectGraphHelperSpecs.cs +++ b/src/Machine.Specifications.Should.Specs/Reflection/ObjectGraphSpecs.cs @@ -1,17 +1,18 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Machine.Specifications.Reflection; -using Machine.Specifications.Utility; - -namespace Machine.Specifications.Should.Specs.Utility +namespace Machine.Specifications.Should.Specs.Reflection { - [Subject(typeof(ObjectGraphHelper))] + [Subject(typeof(ObjectGraph))] class when_getting_an_object_graph { static object subject; - static ObjectGraphHelper.INode result; + static INode result; class with_property { @@ -19,16 +20,16 @@ class with_property subject = new { Property = "value" }; Because of = () => - result = ObjectGraphHelper.GetGraph(subject); + result = ObjectGraph.Get(subject); It should_have_property = () => - ((ObjectGraphHelper.KeyValueNode)result).KeyValues.Where(m => m.Name == "Property").ShouldNotBeEmpty(); + ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Property").ShouldNotBeEmpty(); It should_retrieve_value = () => - ((ObjectGraphHelper.KeyValueNode)result).KeyValues.Where(m => m.Name == "Property").Single().ValueGetter().ShouldEqual("value"); + ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Property").Single().ValueGetter().ShouldEqual("value"); It should_return_a_key_value_node = () => - result.ShouldBeOfExactType(); + result.ShouldBeOfExactType(); } class with_field @@ -41,16 +42,16 @@ class with_field }; Because of = () => - result = ObjectGraphHelper.GetGraph(subject); + result = ObjectGraph.Get(subject); It should_have_field = () => - ((ObjectGraphHelper.KeyValueNode)result).KeyValues.Where(m => m.Name == "Field").ShouldNotBeEmpty(); + ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Field").ShouldNotBeEmpty(); It should_retrieve_value = () => - ((ObjectGraphHelper.KeyValueNode)result).KeyValues.Where(m => m.Name == "Field").Single().ValueGetter().ShouldEqual("value"); + ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Field").Single().ValueGetter().ShouldEqual("value"); It should_return_a_key_value_node = () => - result.ShouldBeOfExactType(); + result.ShouldBeOfExactType(); } class with_readonly_field @@ -59,16 +60,16 @@ class with_readonly_field subject = new ObjectWithReadOnlyField("value"); Because of = () => - result = ObjectGraphHelper.GetGraph(subject); + result = ObjectGraph.Get(subject); It should_have_field = () => - ((ObjectGraphHelper.KeyValueNode)result).KeyValues.Where(m => m.Name == "Field").ShouldNotBeEmpty(); + ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Field").ShouldNotBeEmpty(); It should_retrieve_value = () => - ((ObjectGraphHelper.KeyValueNode)result).KeyValues.Where(m => m.Name == "Field").Single().ValueGetter().ShouldEqual("value"); + ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Field").Single().ValueGetter().ShouldEqual("value"); It should_return_a_key_value_node = () => - result.ShouldBeOfExactType(); + result.ShouldBeOfExactType(); } class with_property_and_field @@ -82,34 +83,34 @@ class with_property_and_field }; Because of = () => - result = ObjectGraphHelper.GetGraph(subject); + result = ObjectGraph.Get(subject); It should_have_field = () => - ((ObjectGraphHelper.KeyValueNode)result).KeyValues.Where(m => m.Name == "Field").ShouldNotBeEmpty(); + ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Field").ShouldNotBeEmpty(); It should_have_property = () => - ((ObjectGraphHelper.KeyValueNode)result).KeyValues.Where(m => m.Name == "Property").ShouldNotBeEmpty(); + ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Property").ShouldNotBeEmpty(); It should_retrieve_field_value = () => - ((ObjectGraphHelper.KeyValueNode)result).KeyValues.Where(m => m.Name == "Field").Single().ValueGetter().ShouldEqual("value2"); + ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Field").Single().ValueGetter().ShouldEqual("value2"); It should_retrieve_property_value = () => - ((ObjectGraphHelper.KeyValueNode)result).KeyValues.Where(m => m.Name == "Property").Single().ValueGetter().ShouldEqual("value1"); + ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Property").Single().ValueGetter().ShouldEqual("value1"); It should_return_a_key_value_node = () => - result.ShouldBeOfExactType(); + result.ShouldBeOfExactType(); } } - [Subject(typeof(ObjectGraphHelper))] + [Subject(typeof(ObjectGraph))] class when_getting_a_sequence_graph_with_an_array { static object array; - static ObjectGraphHelper.INode result; + static INode result; Because of = () => - result = ObjectGraphHelper.GetGraph(array); + result = ObjectGraph.Get(array); public class with_no_values { @@ -117,10 +118,10 @@ public class with_no_values array = new string[] { }; It should_return_an_array_node = () => - result.ShouldBeOfExactType(); + result.ShouldBeOfExactType(); It should_be_empty = () => - ((ObjectGraphHelper.SequenceNode)result).ValueGetters.ShouldBeEmpty(); + ((SequenceNode) result).ValueGetters.ShouldBeEmpty(); } class with_values @@ -129,25 +130,25 @@ class with_values array = new[] { "value1", "value2" }; It should_return_an_array_node = () => - result.ShouldBeOfExactType(); + result.ShouldBeOfExactType(); It should_contain_value1 = () => - ((ObjectGraphHelper.SequenceNode)result).ValueGetters.ElementAt(0)().ShouldEqual("value1"); + ((SequenceNode) result).ValueGetters.ElementAt(0)().ShouldEqual("value1"); It should_contain_value2 = () => - ((ObjectGraphHelper.SequenceNode)result).ValueGetters.ElementAt(1)().ShouldEqual("value2"); + ((SequenceNode) result).ValueGetters.ElementAt(1)().ShouldEqual("value2"); } } - [Subject(typeof(ObjectGraphHelper))] + [Subject(typeof(ObjectGraph))] class when_getting_a_sequence_graph_with_an_enumerable { static IEnumerable sequence; - static ObjectGraphHelper.INode result; + static INode result; Because of = () => - result = ObjectGraphHelper.GetGraph(sequence); + result = ObjectGraph.Get(sequence); class when_no_values { @@ -155,10 +156,10 @@ class when_no_values sequence = new List(); It should_return_an_array_node = () => - result.ShouldBeOfExactType(); + result.ShouldBeOfExactType(); It should_be_empty = () => - ((ObjectGraphHelper.SequenceNode)result).ValueGetters.ShouldBeEmpty(); + ((SequenceNode) result).ValueGetters.ShouldBeEmpty(); } class when_values @@ -167,25 +168,25 @@ class when_values sequence = new List { "value1", "value2" }; It should_return_an_array_node = () => - result.ShouldBeOfExactType(); + result.ShouldBeOfExactType(); It should_contain_value1 = () => - ((ObjectGraphHelper.SequenceNode)result).ValueGetters.ElementAt(0)().ShouldEqual("value1"); + ((SequenceNode) result).ValueGetters.ElementAt(0)().ShouldEqual("value1"); It should_contain_value2 = () => - ((ObjectGraphHelper.SequenceNode)result).ValueGetters.ElementAt(1)().ShouldEqual("value2"); + ((SequenceNode) result).ValueGetters.ElementAt(1)().ShouldEqual("value2"); } } - [Subject(typeof(ObjectGraphHelper))] + [Subject(typeof(ObjectGraph))] class when_getting_a_literal_graph { static object literal; - static ObjectGraphHelper.INode result; + static INode result; Because of = () => - result = ObjectGraphHelper.GetGraph(literal); + result = ObjectGraph.Get(literal); class with_a_string { @@ -193,10 +194,10 @@ class with_a_string literal = "value"; It should_return_a_literal_node = () => - result.ShouldBeOfExactType(); + result.ShouldBeOfExactType(); It should_have_value = () => - ((ObjectGraphHelper.LiteralNode)result).Value.ShouldEqual("value"); + ((LiteralNode) result).Value.ShouldEqual("value"); } class with_an_int @@ -205,14 +206,14 @@ class with_an_int literal = 5; It should_return_a_literal_node = () => - result.ShouldBeOfExactType(); + result.ShouldBeOfExactType(); It should_have_value = () => - ((ObjectGraphHelper.LiteralNode)result).Value.ShouldEqual(5); + ((LiteralNode) result).Value.ShouldEqual(5); } } - [Subject(typeof(ObjectGraphHelper))] + [Subject(typeof(ObjectGraph))] class when_expected_inner_value_is_null { static Model actual_model; @@ -220,7 +221,7 @@ class when_expected_inner_value_is_null static Exception thrown_exception; Establish ctx = () => - actual_model = new Model {Inner = new InnerModel()}; + actual_model = new Model { Inner = new InnerModel() }; Because of = () => thrown_exception = Catch.Exception(() => actual_model.ShouldBeLike(new Model() { Inner = null })); @@ -229,7 +230,7 @@ class when_expected_inner_value_is_null thrown_exception.ShouldBeOfExactType(); } - [Subject(typeof(ObjectGraphHelper))] + [Subject(typeof(ObjectGraph))] class when_actual_inner_value_is_null { static Model actual_model; @@ -246,7 +247,7 @@ class when_actual_inner_value_is_null thrown_exception.ShouldBeOfExactType(); } - [Subject(typeof(ObjectGraphHelper))] + [Subject(typeof(ObjectExtensions))] class when_actual_and_expected_value_are_null { static Model actual_model; @@ -290,5 +291,7 @@ public class Model public InnerModel Inner { get; set; } } - public class InnerModel { } + public class InnerModel + { + } } diff --git a/src/Machine.Specifications.Should/BooleanExtensions.cs b/src/Machine.Specifications.Should/BooleanExtensions.cs index 64ca2d20..fd75cba5 100644 --- a/src/Machine.Specifications.Should/BooleanExtensions.cs +++ b/src/Machine.Specifications.Should/BooleanExtensions.cs @@ -1,11 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Text; -using JetBrains.Annotations; +using JetBrains.Annotations; namespace Machine.Specifications { - internal static class BooleanExtensions + public static class BooleanExtensions { [AssertionMethod] public static void ShouldBeFalse([AssertionCondition(AssertionConditionType.IS_FALSE)] this bool condition) diff --git a/src/Machine.Specifications.Should/ComparableExtensions.cs b/src/Machine.Specifications.Should/ComparableExtensions.cs index 355e78aa..0acc3d1b 100644 --- a/src/Machine.Specifications.Should/ComparableExtensions.cs +++ b/src/Machine.Specifications.Should/ComparableExtensions.cs @@ -1,10 +1,11 @@ using System; -using System.Collections.Generic; -using System.Text; +using System.Linq; +using Machine.Specifications.Text; +using Machine.Specifications.Utility.Internal; namespace Machine.Specifications { - internal static class ComparableExtensions + public static class ComparableExtensions { public static IComparable ShouldBeGreaterThan(this IComparable arg1, IComparable arg2) { @@ -97,5 +98,15 @@ private static object TryToChangeType(this object original, Type type) return original; } } + + private static SpecificationException NewException(string message, params object[] parameters) + { + if (parameters.Any()) + { + return new SpecificationException(string.Format(message.EnsureSafeFormat(), parameters.Select(x => x.ToUsefulString()).Cast().ToArray())); + } + + return new SpecificationException(message); + } } } diff --git a/src/Machine.Specifications.Should/DateTimeExtensions.cs b/src/Machine.Specifications.Should/DateTimeExtensions.cs index a2b9d078..99e514fa 100644 --- a/src/Machine.Specifications.Should/DateTimeExtensions.cs +++ b/src/Machine.Specifications.Should/DateTimeExtensions.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; -using System.Text; -using Machine.Specifications.Utility.Internal; +using Machine.Specifications.Text; namespace Machine.Specifications { - internal static class DateTimeExtensions + public static class DateTimeExtensions { public static void ShouldBeCloseTo(this TimeSpan actual, TimeSpan expected, TimeSpan tolerance) { diff --git a/src/Machine.Specifications.Should/EnumerableExtensions.cs b/src/Machine.Specifications.Should/EnumerableExtensions.cs index 918db99d..238237d1 100644 --- a/src/Machine.Specifications.Should/EnumerableExtensions.cs +++ b/src/Machine.Specifications.Should/EnumerableExtensions.cs @@ -3,12 +3,12 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Text; +using Machine.Specifications.Text; using Machine.Specifications.Utility.Internal; namespace Machine.Specifications { - internal static class EnumerableExtensions + public static class EnumerableExtensions { public static void ShouldEachConformTo(this IEnumerable list, Expression> condition) { @@ -196,5 +196,15 @@ public static void ShouldContainOnly(this IEnumerable list, IEnumerable throw new SpecificationException(message); } } + + private static SpecificationException NewException(string message, params object[] parameters) + { + if (parameters.Any()) + { + return new SpecificationException(string.Format(message.EnsureSafeFormat(), parameters.Select(x => x.ToUsefulString()).Cast().ToArray())); + } + + return new SpecificationException(message); + } } } diff --git a/src/Machine.Specifications.Should/ExceptionExtensions.cs b/src/Machine.Specifications.Should/ExceptionExtensions.cs index 8297e484..1f6eb217 100644 --- a/src/Machine.Specifications.Should/ExceptionExtensions.cs +++ b/src/Machine.Specifications.Should/ExceptionExtensions.cs @@ -1,10 +1,8 @@ using System; -using System.Collections.Generic; -using System.Text; namespace Machine.Specifications { - internal static class ExceptionExtensions + public static class ExceptionExtensions { public static void ShouldContainErrorMessage(this Exception exception, string expected) { diff --git a/src/Machine.Specifications.Should/FloatingPointExtensions.cs b/src/Machine.Specifications.Should/FloatingPointExtensions.cs index a55a49a9..8ff8e704 100644 --- a/src/Machine.Specifications.Should/FloatingPointExtensions.cs +++ b/src/Machine.Specifications.Should/FloatingPointExtensions.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; -using System.Text; -using Machine.Specifications.Utility.Internal; +using Machine.Specifications.Text; namespace Machine.Specifications { - internal static class FloatingPointExtensions + public static class FloatingPointExtensions { public static void ShouldBeCloseTo(this float actual, float expected) { diff --git a/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj b/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj index 65f32f06..66ab3eca 100644 --- a/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj +++ b/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj @@ -28,8 +28,4 @@ - - - - diff --git a/src/Machine.Specifications.Should/ObjectExtensions.cs b/src/Machine.Specifications.Should/ObjectExtensions.cs index 90561d15..45fe3e26 100644 --- a/src/Machine.Specifications.Should/ObjectExtensions.cs +++ b/src/Machine.Specifications.Should/ObjectExtensions.cs @@ -2,14 +2,14 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Text; using JetBrains.Annotations; -using Machine.Specifications.Utility; +using Machine.Specifications.Reflection; +using Machine.Specifications.Text; using Machine.Specifications.Utility.Internal; namespace Machine.Specifications { - internal static class ObjectExtensions + public static class ObjectExtensions { [AssertionMethod] public static void ShouldBeNull([AssertionCondition(AssertionConditionType.IS_NULL)] this object anObject) @@ -131,6 +131,16 @@ public static void ShouldBeLike(this object obj, object expected) } } + private static SpecificationException NewException(string message, params object[] parameters) + { + if (parameters.Any()) + { + return new SpecificationException(string.Format(message.EnsureSafeFormat(), parameters.Select(x => x.ToUsefulString()).Cast().ToArray())); + } + + return new SpecificationException(message); + } + private static IEnumerable ShouldBeLikeInternal(object obj, object expected, string nodeName, HashSet visited) { // Stop at already checked -pairs to prevent infinite loops (cycles in object graphs). Additionally @@ -144,17 +154,17 @@ private static IEnumerable ShouldBeLikeInternal(object o visited.Add(objExpectedTuple); - ObjectGraphHelper.INode expectedNode = null; + var expectedNode = default(INode); - var nodeType = typeof(ObjectGraphHelper.LiteralNode); + var nodeType = typeof(LiteralNode); if (obj != null && expected != null) { - expectedNode = ObjectGraphHelper.GetGraph(expected); + expectedNode = ObjectGraph.Get(expected); nodeType = expectedNode.GetType(); } - if (nodeType == typeof(ObjectGraphHelper.LiteralNode)) + if (nodeType == typeof(LiteralNode)) { try { @@ -168,7 +178,7 @@ private static IEnumerable ShouldBeLikeInternal(object o return Enumerable.Empty(); } - if (nodeType == typeof(ObjectGraphHelper.SequenceNode)) + if (nodeType == typeof(SequenceNode)) { if (obj == null) { @@ -177,17 +187,17 @@ private static IEnumerable ShouldBeLikeInternal(object o return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; } - var actualNode = ObjectGraphHelper.GetGraph(obj); + var actualNode = ObjectGraph.Get(obj); - if (actualNode.GetType() != typeof(ObjectGraphHelper.SequenceNode)) + if (actualNode.GetType() != typeof(SequenceNode)) { var errorMessage = $" Expected: Array or Sequence{Environment.NewLine} But was: {obj.GetType()}"; return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; } - var expectedValues = ((ObjectGraphHelper.SequenceNode) expectedNode)?.ValueGetters.ToArray(); - var actualValues = ((ObjectGraphHelper.SequenceNode) actualNode).ValueGetters.ToArray(); + var expectedValues = ((SequenceNode) expectedNode)?.ValueGetters.ToArray(); + var actualValues = ((SequenceNode) actualNode).ValueGetters.ToArray(); var expectedCount = expectedValues?.Length ?? 0; var actualCount = actualValues.Length; @@ -203,19 +213,19 @@ private static IEnumerable ShouldBeLikeInternal(object o .SelectMany(i => ShouldBeLikeInternal(actualValues.ElementAt(i)(), expectedValues?.ElementAt(i)(), $"{nodeName}[{i}]", visited)); } - if (nodeType == typeof(ObjectGraphHelper.KeyValueNode)) + if (nodeType == typeof(KeyValueNode)) { - var actualNode = ObjectGraphHelper.GetGraph(obj); + var actualNode = ObjectGraph.Get(obj); - if (actualNode.GetType() != typeof(ObjectGraphHelper.KeyValueNode)) + if (actualNode.GetType() != typeof(KeyValueNode)) { var errorMessage = $" Expected: Class{Environment.NewLine} But was: {obj?.GetType()}"; return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; } - var expectedKeyValues = ((ObjectGraphHelper.KeyValueNode) expectedNode)?.KeyValues; - var actualKeyValues = ((ObjectGraphHelper.KeyValueNode) actualNode).KeyValues; + var expectedKeyValues = ((KeyValueNode) expectedNode)?.KeyValues; + var actualKeyValues = ((KeyValueNode) actualNode).KeyValues; return expectedKeyValues?.SelectMany(kv => { @@ -241,19 +251,19 @@ private static IEnumerable ShouldBeLikeInternal(object o private class ReferentialEqualityTuple { - private readonly object obj; + private readonly object value; private readonly object expected; - public ReferentialEqualityTuple(object obj, object expected) + public ReferentialEqualityTuple(object value, object expected) { - this.obj = obj; + this.value = value; this.expected = expected; } public override int GetHashCode() { - return RuntimeHelpers.GetHashCode(obj) * RuntimeHelpers.GetHashCode(expected); + return RuntimeHelpers.GetHashCode(value) * RuntimeHelpers.GetHashCode(expected); } public override bool Equals(object other) @@ -263,7 +273,7 @@ public override bool Equals(object other) return false; } - return ReferenceEquals(obj, otherSimpleTuple.obj) && ReferenceEquals(expected, otherSimpleTuple.expected); + return ReferenceEquals(value, otherSimpleTuple.value) && ReferenceEquals(expected, otherSimpleTuple.expected); } } } diff --git a/src/Machine.Specifications.Should/Reflection/INode.cs b/src/Machine.Specifications.Should/Reflection/INode.cs new file mode 100644 index 00000000..095f587f --- /dev/null +++ b/src/Machine.Specifications.Should/Reflection/INode.cs @@ -0,0 +1,6 @@ +namespace Machine.Specifications.Reflection +{ + internal interface INode + { + } +} diff --git a/src/Machine.Specifications.Should/Reflection/KeyValueNode.cs b/src/Machine.Specifications.Should/Reflection/KeyValueNode.cs new file mode 100644 index 00000000..31129426 --- /dev/null +++ b/src/Machine.Specifications.Should/Reflection/KeyValueNode.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Machine.Specifications.Reflection +{ + internal class KeyValueNode : INode + { + public IEnumerable KeyValues { get; set; } + } +} diff --git a/src/Machine.Specifications.Should/Reflection/LiteralNode.cs b/src/Machine.Specifications.Should/Reflection/LiteralNode.cs new file mode 100644 index 00000000..ff9d405f --- /dev/null +++ b/src/Machine.Specifications.Should/Reflection/LiteralNode.cs @@ -0,0 +1,7 @@ +namespace Machine.Specifications.Reflection +{ + internal class LiteralNode : INode + { + public object Value { get; set; } + } +} diff --git a/src/Machine.Specifications.Should/Reflection/Member.cs b/src/Machine.Specifications.Should/Reflection/Member.cs new file mode 100644 index 00000000..d0ed44f8 --- /dev/null +++ b/src/Machine.Specifications.Should/Reflection/Member.cs @@ -0,0 +1,11 @@ +using System; + +namespace Machine.Specifications.Reflection +{ + internal class Member + { + public string Name { get; set; } + + public Func ValueGetter { get; set; } + } +} diff --git a/src/Machine.Specifications.Should/Reflection/ObjectGraph.cs b/src/Machine.Specifications.Should/Reflection/ObjectGraph.cs new file mode 100644 index 00000000..e81ce95d --- /dev/null +++ b/src/Machine.Specifications.Should/Reflection/ObjectGraph.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections; +using System.Linq; + +namespace Machine.Specifications.Reflection +{ + internal static class ObjectGraph + { + public static INode Get(object value) + { + var type = value.GetType(); + + if (type.IsArray || value is IEnumerable && type != typeof(string)) + { + return GetSequenceNode((IEnumerable) value); + } + + if (type.IsClass && type != typeof(string)) + { + return GetKeyValueNode(value); + } + + return new LiteralNode + { + Value = value + }; + } + + private static INode GetSequenceNode(IEnumerable value) + { + var getters = value.Cast() + .Select>(x => () => x) + .ToArray(); + + return new SequenceNode + { + ValueGetters = getters + }; + } + + private static INode GetKeyValueNode(object value) + { + var type = value.GetType(); + + var properties = type + .GetProperties() + .Where(x => x.CanRead && !x.GetGetMethod().IsStatic) + .Select(x => + { + return new Member + { + Name = x.Name, + ValueGetter = () => x.GetValue(value, null) + }; + }); + + var fields = type + .GetFields() + .Select(x => + { + return new Member + { + Name = x.Name, + ValueGetter = () => x.GetValue(value) + }; + }); + + return new KeyValueNode + { + KeyValues = properties.Concat(fields).OrderBy(m => m.Name) + }; + } + } +} diff --git a/src/Machine.Specifications.Should/Reflection/SequenceNode.cs b/src/Machine.Specifications.Should/Reflection/SequenceNode.cs new file mode 100644 index 00000000..1790e796 --- /dev/null +++ b/src/Machine.Specifications.Should/Reflection/SequenceNode.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace Machine.Specifications.Reflection +{ + internal class SequenceNode : INode + { + public IEnumerable> ValueGetters { get; set; } + } +} diff --git a/src/Machine.Specifications.Should/ShouldExtensionMethods.cs b/src/Machine.Specifications.Should/ShouldExtensionMethods.cs index 8c0be8b2..cdb24236 100644 --- a/src/Machine.Specifications.Should/ShouldExtensionMethods.cs +++ b/src/Machine.Specifications.Should/ShouldExtensionMethods.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Linq.Expressions; +using Machine.Specifications.Text; using Machine.Specifications.Utility.Internal; namespace Machine.Specifications diff --git a/src/Machine.Specifications.Should/StringExtensions.cs b/src/Machine.Specifications.Should/StringExtensions.cs index 1433e19a..3c7c19c6 100644 --- a/src/Machine.Specifications.Should/StringExtensions.cs +++ b/src/Machine.Specifications.Should/StringExtensions.cs @@ -1,11 +1,12 @@ using System; -using System.Collections.Generic; -using System.Text; +using System.Globalization; +using System.Linq; using System.Text.RegularExpressions; +using Machine.Specifications.Text; namespace Machine.Specifications { - internal static class StringExtensions + public static class StringExtensions { public static void ShouldBeEmpty(this string aString) { @@ -164,5 +165,15 @@ public static void ShouldBeSurroundedWith(this string actual, string expectedDel actual.ShouldStartWith(expectedDelimiter); actual.ShouldEndWith(expectedDelimiter); } + + private static SpecificationException NewException(string message, params object[] parameters) + { + if (parameters.Any()) + { + return new SpecificationException(string.Format(message.EnsureSafeFormat(), parameters.Select(x => x.ToUsefulString()).Cast().ToArray())); + } + + return new SpecificationException(message); + } } } diff --git a/src/Machine.Specifications.Should/Text/EnumerableExtensions.cs b/src/Machine.Specifications.Should/Text/EnumerableExtensions.cs new file mode 100644 index 00000000..807fc3a6 --- /dev/null +++ b/src/Machine.Specifications.Should/Text/EnumerableExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Machine.Specifications.Text +{ + internal static class EnumerableExtensions + { + public static string EachToUsefulString(this IEnumerable enumerable) + { + var array = enumerable.ToArray(); + var arrayValues = array.Select(x => x.ToUsefulString().Indent()) + .Take(10) + .ToArray(); + + var result = new StringBuilder() + .AppendLine("{") + .Append(string.Join(",\r\n", arrayValues)); + + if (array.Length > 10) + { + if (array.Length > 11) + { + result.AppendLine($",\r\n ...({array.Length - 10} more elements)"); + } + else + { + result.AppendLine(",\r\n" + array.Last().ToUsefulString().Indent()); + } + } + else + { + result.AppendLine(); + } + + return result.AppendLine("}").ToString(); + } + } +} diff --git a/src/Machine.Specifications.Should/Text/ObjectExtensions.cs b/src/Machine.Specifications.Should/Text/ObjectExtensions.cs new file mode 100644 index 00000000..e9a46765 --- /dev/null +++ b/src/Machine.Specifications.Should/Text/ObjectExtensions.cs @@ -0,0 +1,58 @@ +using System.Collections; +using System.Linq; +using System.Reflection; + +namespace Machine.Specifications.Text +{ + internal static class ObjectExtensions + { + internal static string ToUsefulString(this object value) + { + if (value == null) + { + return "[null]"; + } + + if (value is string stringValue) + { + return "\"" + stringValue.Replace("\n", "\\n") + "\""; + } + + if (value.GetType().GetTypeInfo().IsValueType) + { + return "[" + value + "]"; + } + + if (value is IEnumerable items) + { + var enumerable = items.Cast(); + + return items.GetType() + ":\r\n" + enumerable.EachToUsefulString(); + } + + var str = value.ToString(); + + if (str == null || str.Trim() == string.Empty) + { + return $"{value.GetType()}:[]"; + } + + str = str.Trim(); + + if (str.Contains("\n")) + { + return string.Format(@"{1}: +[ +{0} +]", str.Indent(), value.GetType()); + } + + if (value.GetType().ToString() == str) + { + return value.GetType().ToString(); + } + + return $"{value.GetType()}:[{str}]"; + } + } +} diff --git a/src/Machine.Specifications.Should/Text/StringExtensions.cs b/src/Machine.Specifications.Should/Text/StringExtensions.cs new file mode 100644 index 00000000..22fdf77a --- /dev/null +++ b/src/Machine.Specifications.Should/Text/StringExtensions.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace Machine.Specifications.Text +{ + internal static class StringExtensions + { + private const string EscapedBraces = "{{{0}}}"; + + private static readonly Regex FormatBraces = new Regex(@"{([^\d].*?)}", RegexOptions.Compiled | RegexOptions.Singleline); + + private static readonly string[] Delimiters = + { + "\r\n", + "\n" + }; + + public static string Indent(this string value) + { + if (string.IsNullOrEmpty(value)) + { + return string.Empty; + } + + var parts = value.Split(Delimiters, StringSplitOptions.None); + var result = new StringBuilder($" {parts[0]}"); + + foreach (var part in parts.Skip(1)) + { + result.AppendLine(); + result.Append(" " + part); + } + + return result.ToString(); + } + + public static string EnsureSafeFormat(this string message) + { + return FormatBraces.Replace(message, match => string.Format(EscapedBraces, match.Groups[0])); + } + } +} diff --git a/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs b/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs index 7ebd0a59..388971de 100644 --- a/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs +++ b/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs @@ -1,32 +1,14 @@ using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Reflection; +using Machine.Specifications.Text; namespace Machine.Specifications.Utility.Internal { internal static class PrettyPrintingExtensions { - private const string CurlyBraceSurround = "{{{0}}}"; - - private static readonly Regex EscapeNonFormatBraces = new Regex(@"{([^\d].*?)}", RegexOptions.Compiled | RegexOptions.Singleline); - public static string FormatErrorMessage(object actualObject, object expectedObject) { - if (!(actualObject is string) || !(expectedObject is string)) - { - // format objects - var actual = actualObject.ToUsefulString(); - var expected = expectedObject.ToUsefulString(); - - return string.Format(" Expected: {1}{0} But was: {2}", Environment.NewLine, expected, actual); - } - else + if (actualObject is string && expectedObject is string) { - // format strings var actual = actualObject as string; var expected = expectedObject as string; @@ -49,6 +31,11 @@ public static string FormatErrorMessage(object actualObject, object expectedObje actualReported, new string('-', count)); } + + var actualValue = actualObject.ToUsefulString(); + var expectedValue = expectedObject.ToUsefulString(); + + return string.Format(" Expected: {1}{0} But was: {2}", Environment.NewLine, expectedValue, actualValue); } private static void GetStringsAroundFirstDifference(string expected, string actual, int firstIndexOfFirstDifference, out string actualReported, out string expectedReported) @@ -142,109 +129,5 @@ private static string GetExpectedStringLengthMessage(int actual, int expected) return $"Expected string length {actual} but was {expected}."; } - - private static string Tab(this string str) - { - if (string.IsNullOrEmpty(str)) - { - return string.Empty; - } - - var split = str.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); - var sb = new StringBuilder(); - - sb.Append(" " + split[0]); - - foreach (var part in split.Skip(1)) - { - sb.AppendLine(); - sb.Append(" " + part); - } - - return sb.ToString(); - } - - public static string EachToUsefulString(this IEnumerable enumerable) - { - var array = enumerable.ToArray(); - - var sb = new StringBuilder(); - sb.AppendLine("{"); - sb.Append(string.Join(",\r\n", array.Select(x => x.ToUsefulString().Tab()).Take(10).ToArray())); - - if (array.Length > 10) - { - if (array.Length > 11) - { - sb.AppendLine($",\r\n ...({array.Length - 10} more elements)"); - } - else - { - sb.AppendLine(",\r\n" + array.Last().ToUsefulString().Tab()); - } - } - else - { - sb.AppendLine(); - } - - sb.AppendLine("}"); - - return sb.ToString(); - } - - internal static string ToUsefulString(this object obj) - { - if (obj == null) - { - return "[null]"; - } - - if (obj is string value) - { - return "\"" + value.Replace("\n", "\\n") + "\""; - } - - if (obj.GetType().GetTypeInfo().IsValueType) - { - return "[" + obj + "]"; - } - - if (obj is IEnumerable items) - { - var enumerable = items.Cast(); - - return items.GetType() + ":\r\n" + enumerable.EachToUsefulString(); - } - - var str = obj.ToString(); - - if (str == null || str.Trim() == string.Empty) - { - return $"{obj.GetType()}:[]"; - } - - str = str.Trim(); - - if (str.Contains("\n")) - { - return string.Format(@"{1}: -[ -{0} -]", str.Tab(), obj.GetType()); - } - - if (obj.GetType().ToString() == str) - { - return obj.GetType().ToString(); - } - - return $"{obj.GetType()}:[{str}]"; - } - - internal static string EnsureSafeFormat(this string message) - { - return EscapeNonFormatBraces.Replace(message, match => string.Format(CurlyBraceSurround, match.Groups[0])); - } } } diff --git a/src/Machine.Specifications.Should/Utility/ObjectGraphHelper.cs b/src/Machine.Specifications.Should/Utility/ObjectGraphHelper.cs deleted file mode 100644 index 88ae3fb9..00000000 --- a/src/Machine.Specifications.Should/Utility/ObjectGraphHelper.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace Machine.Specifications.Utility -{ - /// - /// Modified from Steve Wagner's ObjectDiff prototype https://gist.github.com/2841822 - /// - internal static class ObjectGraphHelper - { - public static INode GetGraph(object obj) - { - var objectType = obj.GetType(); - - if (objectType.IsArray || obj is IEnumerable && objectType != typeof(string)) - { - return GetSequenceNode(obj); - } - - if (objectType.GetTypeInfo().IsClass && objectType != typeof(string)) - { - return GetKeyValueNode(obj); - } - - return new LiteralNode - { - Value = obj - }; - } - - private static INode GetSequenceNode(object obj) - { - var sequence = ((IEnumerable) obj).Cast(); - - return new SequenceNode - { - ValueGetters = sequence.Select>(a => () => a).ToArray() - }; - } - - private static INode GetKeyValueNode(object obj) - { - var properties = obj.GetType() - .GetProperties() - .Where(p => p.CanRead) - .Select(p => - { - return new Member - { - Name = p.Name, - ValueGetter = () => p.GetValue(obj, null) - }; - }); - - var fields = obj.GetType() - .GetFields() - .Select(f => - { - return new Member - { - Name = f.Name, - ValueGetter = () => f.GetValue(obj) - }; - }); - - return new KeyValueNode - { - KeyValues = properties.Concat(fields).OrderBy(m => m.Name) - }; - } - - public class SequenceNode : INode - { - public IEnumerable> ValueGetters { get; set; } - } - - public interface INode - { - } - - public class KeyValueNode : INode - { - public IEnumerable KeyValues { get; set; } - } - - public class LiteralNode : INode - { - public object Value { get; set; } - } - - public class Member - { - public string Name { get; set; } - - public Func ValueGetter { get; set; } - } - } -} From 9dabb18fc2d9e850f26fc05f09efddf3654f1a28 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Tue, 7 Dec 2021 10:45:14 +0800 Subject: [PATCH 3/9] refactoring --- .../AssertionSpecs.cs | 26 ++-- ...Machine.Specifications.Should.Specs.csproj | 1 + .../Reflection/ObjectGraphSpecs.cs | 2 +- .../ShouldBeCloseToSpecs.cs | 2 +- .../ShouldBeLikeSpecs.cs | 19 +-- .../AssertComparer.cs | 35 ------ .../ComparableExtensions.cs | 19 +-- .../Comparers/AssertEqualityComparer.cs | 40 ++++++ .../Comparers/ComparableComparer.cs | 14 +-- .../Comparers/ComparerFactory.cs | 25 ---- .../Comparers/ComparisionResult.cs | 19 --- .../Comparers/DefaultComparer.cs | 14 ++- .../Comparers/EnumerableComparer.cs | 10 +- .../Comparers/EquatableComparer.cs | 8 +- .../Comparers/GenericTypeComparer.cs | 23 ++-- .../Comparers/IComparerStrategy.cs | 7 -- .../Comparers/IEqualityComparerStrategy.cs | 7 ++ .../Comparers/NoResult.cs | 10 -- .../Comparers/ObjectExtension.cs | 10 -- .../Comparers/TypeComparer.cs | 8 +- .../Comparers/TypeExtension.cs | 12 -- .../DateTimeExtensions.cs | 2 +- .../EnumerableExtensions.cs | 15 +-- .../EqualityExtensions.cs | 55 +++++++++ ...Extensions.cs => EquivalenceExtensions.cs} | 115 +----------------- .../FloatingPointExtensions.cs | 2 +- .../EnumerableExtensions.cs | 2 +- .../{Text => Formatting}/ObjectExtensions.cs | 2 +- .../Formatting/ObjectFormatter.cs | 61 ++++++++++ .../{Text => Formatting}/StringExtensions.cs | 2 +- .../NullExtensions.cs | 26 ++++ .../ShouldExtensionMethods.cs | 60 --------- .../StringExtensions.cs | 14 +-- .../TypeExtensions.cs | 79 ++++++++++++ .../Internal/PrettyPrintingExtensions.cs | 4 +- 35 files changed, 362 insertions(+), 388 deletions(-) delete mode 100644 src/Machine.Specifications.Should/AssertComparer.cs create mode 100644 src/Machine.Specifications.Should/Comparers/AssertEqualityComparer.cs delete mode 100644 src/Machine.Specifications.Should/Comparers/ComparerFactory.cs delete mode 100644 src/Machine.Specifications.Should/Comparers/ComparisionResult.cs delete mode 100644 src/Machine.Specifications.Should/Comparers/IComparerStrategy.cs create mode 100644 src/Machine.Specifications.Should/Comparers/IEqualityComparerStrategy.cs delete mode 100644 src/Machine.Specifications.Should/Comparers/NoResult.cs delete mode 100644 src/Machine.Specifications.Should/Comparers/ObjectExtension.cs delete mode 100644 src/Machine.Specifications.Should/Comparers/TypeExtension.cs create mode 100644 src/Machine.Specifications.Should/EqualityExtensions.cs rename src/Machine.Specifications.Should/{ObjectExtensions.cs => EquivalenceExtensions.cs} (62%) rename src/Machine.Specifications.Should/{Text => Formatting}/EnumerableExtensions.cs (96%) rename src/Machine.Specifications.Should/{Text => Formatting}/ObjectExtensions.cs (96%) create mode 100644 src/Machine.Specifications.Should/Formatting/ObjectFormatter.cs rename src/Machine.Specifications.Should/{Text => Formatting}/StringExtensions.cs (96%) create mode 100644 src/Machine.Specifications.Should/NullExtensions.cs delete mode 100644 src/Machine.Specifications.Should/ShouldExtensionMethods.cs create mode 100644 src/Machine.Specifications.Should/TypeExtensions.cs diff --git a/src/Machine.Specifications.Should.Specs/AssertionSpecs.cs b/src/Machine.Specifications.Should.Specs/AssertionSpecs.cs index 3dd6c58d..4f418648 100644 --- a/src/Machine.Specifications.Should.Specs/AssertionSpecs.cs +++ b/src/Machine.Specifications.Should.Specs/AssertionSpecs.cs @@ -3,7 +3,7 @@ namespace Machine.Specifications.Should.Specs { - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EnumerableExtensions))] class when_checking_if_a_collection_contains_elements_that_match_a_func { static Exception exception; @@ -20,7 +20,7 @@ class when_checking_if_a_collection_contains_elements_that_match_a_func exception.Message.ShouldContain("Should contain elements conforming to: x => (x > 3)"); } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EnumerableExtensions))] class when_checking_if_a_collection_contains_elements_that_do_not_match_a_func { static Exception exception; @@ -40,7 +40,7 @@ class when_checking_if_a_collection_contains_elements_that_do_not_match_a_func exception.Message.ShouldMatch(@"does contain: {\s+\[1\],\s+\[2\]\s+}"); } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EnumerableExtensions))] class when_checking_if_a_collection_contains_only_elements_that_match_a_func { static Exception exception; @@ -63,7 +63,7 @@ class when_checking_if_a_collection_contains_only_elements_that_match_a_func exception.Message.ShouldMatch(@"the following items did not meet the condition: {\s+\[1\],\s+\[3\]\s+}"); } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EnumerableExtensions))] class when_a_list_contains_an_element_of_another_list { static Exception spec_exception; @@ -88,7 +88,7 @@ class when_a_list_contains_an_element_of_another_list spec_exception.ShouldBeNull(); } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EnumerableExtensions))] class when_a_list_not_contains_an_element_of_another_list { static Exception spec_exception; @@ -117,7 +117,7 @@ class when_a_list_not_contains_an_element_of_another_list spec_exception.ShouldBeOfExactType(); } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(StringExtensions))] class when_a_null_string_is_asserted_on { static string a_string; @@ -138,7 +138,7 @@ class when_a_null_string_is_asserted_on Catch.Exception(() => a_string.ShouldNotBeNull()).ShouldBeOfExactType(); } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(StringExtensions))] class when_an_empty_string_is_asserted_on { static string a_string; @@ -159,7 +159,7 @@ class when_an_empty_string_is_asserted_on Catch.Exception(() => a_string.ShouldNotBeNull()).ShouldBeNull(); } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(StringExtensions))] class when_an_non_empty_string_is_asserted_on { static string a_string; @@ -180,7 +180,7 @@ class when_an_non_empty_string_is_asserted_on Catch.Exception(() => a_string.ShouldNotBeNull()).ShouldBeNull(); } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(TypeExtensions))] class when_a_type_assertion_fails { static string a_string; @@ -197,7 +197,7 @@ class when_a_type_assertion_fails exception.Message.ShouldStartWith("Should be of type System.Int32"); } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EqualityExtensions))] class when_checking_if_an_item_matches_a_func { static Exception exception; @@ -214,7 +214,7 @@ class when_checking_if_an_item_matches_a_func exception.ShouldBeNull(); } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EqualityExtensions))] class when_checking_if_an_item_matches_a_func_and_the_check_fails { static Exception exception; @@ -231,7 +231,7 @@ class when_checking_if_an_item_matches_a_func_and_the_check_fails exception.Message.ShouldContain("Should match expression [x => (x > 50)], but does not."); } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EqualityExtensions))] class when_comparing_different_classes_with_equals_for_equality { static Franc five_francs; @@ -243,7 +243,7 @@ class when_comparing_different_classes_with_equals_for_equality five_francs.Equals(new Money(5, "CHF")).ShouldBeTrue(); } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EqualityExtensions))] class when_comparing_different_classes_with_should_equal_for_equality { static Franc five_francs; diff --git a/src/Machine.Specifications.Should.Specs/Machine.Specifications.Should.Specs.csproj b/src/Machine.Specifications.Should.Specs/Machine.Specifications.Should.Specs.csproj index cf479684..f1f90c72 100644 --- a/src/Machine.Specifications.Should.Specs/Machine.Specifications.Should.Specs.csproj +++ b/src/Machine.Specifications.Should.Specs/Machine.Specifications.Should.Specs.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Machine.Specifications.Should.Specs/Reflection/ObjectGraphSpecs.cs b/src/Machine.Specifications.Should.Specs/Reflection/ObjectGraphSpecs.cs index e78407d3..7cac5e23 100644 --- a/src/Machine.Specifications.Should.Specs/Reflection/ObjectGraphSpecs.cs +++ b/src/Machine.Specifications.Should.Specs/Reflection/ObjectGraphSpecs.cs @@ -247,7 +247,7 @@ class when_actual_inner_value_is_null thrown_exception.ShouldBeOfExactType(); } - [Subject(typeof(ObjectExtensions))] + [Subject(typeof(EquivalenceExtensions))] class when_actual_and_expected_value_are_null { static Model actual_model; diff --git a/src/Machine.Specifications.Should.Specs/ShouldBeCloseToSpecs.cs b/src/Machine.Specifications.Should.Specs/ShouldBeCloseToSpecs.cs index a547aaf8..4ba16acc 100644 --- a/src/Machine.Specifications.Should.Specs/ShouldBeCloseToSpecs.cs +++ b/src/Machine.Specifications.Should.Specs/ShouldBeCloseToSpecs.cs @@ -2,7 +2,7 @@ namespace Machine.Specifications.Should.Specs { - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(FloatingPointExtensions))] class when_asserting_that_a_value_should_be_close_to_another_value { static Exception exception; diff --git a/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs b/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs index d294d5e8..a1ad5fab 100644 --- a/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs +++ b/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; +using Xunit; namespace Machine.Specifications.Should.Specs { - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EqualityExtensions))] class when_asserting_object_is_like_expected_key_values { static Exception exception; @@ -140,7 +141,7 @@ class and_integer_should_equal_object } } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EqualityExtensions))] class when_asserting_object_is_like_expected_array { static Exception exception; @@ -241,7 +242,7 @@ public class Dummy } } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EqualityExtensions))] class when_asserting_object_is_like_expected_nested_object { static Exception exception; @@ -332,7 +333,7 @@ class with_missing_value } } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EqualityExtensions))] class when_asserting_that_two_objects_of_the_same_concrete_type_are_like_each_other { static Exception exception; @@ -400,7 +401,7 @@ class and_the_objects_are_different_and_have_null_values } } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EqualityExtensions))] class when_asserting_that_two_objects_containing_arrays_as_properties_are_like_each_other { static Exception exception; @@ -500,7 +501,7 @@ class and_the_objects_are_different_and_the_actual_object_has_a_null_value } } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EqualityExtensions))] class when_asserting_that_two_objects_containing_collections_as_properties_are_like_each_other { static ListDummy first; @@ -603,7 +604,7 @@ class and_the_objects_differ_and_have_null_values } } - [Subject(typeof (ShouldExtensionMethods))] + [Subject(typeof(EqualityExtensions))] class when_node_with_circular_references { public class Node @@ -775,7 +776,7 @@ class and_the_node_has_indirect_circular_reference } } - [Subject(typeof (ShouldExtensionMethods))] + [Subject(typeof(EqualityExtensions))] class when_node_with_inner_static_node { public class Node @@ -804,7 +805,7 @@ public class Node exception.ShouldBeNull(); } - [Subject(typeof(ShouldExtensionMethods))] + [Subject(typeof(EqualityExtensions))] class when_complex_type_with_circular_references_are_in_collection { public class Node diff --git a/src/Machine.Specifications.Should/AssertComparer.cs b/src/Machine.Specifications.Should/AssertComparer.cs deleted file mode 100644 index 0a4256e3..00000000 --- a/src/Machine.Specifications.Should/AssertComparer.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using Machine.Specifications.Comparers; - -namespace Machine.Specifications -{ - internal class AssertComparer : IComparer, IEqualityComparer - { - public int Compare(T x, T y) - { - var comparers = ComparerFactory.GetComparers(); - - foreach (var comparer in comparers) - { - var comparisionResult = comparer.Compare(x, y); - - if (comparisionResult.FoundResult) - { - return comparisionResult.Result; - } - } - - return ComparerFactory.GetDefaultComparer().Compare(x, y); - } - - public bool Equals(T x, T y) - { - return Compare(x, y) == 0; - } - - public int GetHashCode(T obj) - { - return obj.GetHashCode(); - } - } -} diff --git a/src/Machine.Specifications.Should/ComparableExtensions.cs b/src/Machine.Specifications.Should/ComparableExtensions.cs index 0acc3d1b..40325e43 100644 --- a/src/Machine.Specifications.Should/ComparableExtensions.cs +++ b/src/Machine.Specifications.Should/ComparableExtensions.cs @@ -1,13 +1,12 @@ using System; using System.Linq; -using Machine.Specifications.Text; -using Machine.Specifications.Utility.Internal; +using Machine.Specifications.Formatting; namespace Machine.Specifications { public static class ComparableExtensions { - public static IComparable ShouldBeGreaterThan(this IComparable arg1, IComparable arg2) + public static void ShouldBeGreaterThan(this IComparable arg1, IComparable arg2) { if (arg2 == null) { @@ -23,11 +22,9 @@ public static IComparable ShouldBeGreaterThan(this IComparable arg1, IComparable { throw NewException("Should be greater than {0} but is {1}", arg2, arg1); } - - return arg1; } - public static IComparable ShouldBeGreaterThanOrEqualTo(this IComparable arg1, IComparable arg2) + public static void ShouldBeGreaterThanOrEqualTo(this IComparable arg1, IComparable arg2) { if (arg2 == null) { @@ -43,11 +40,9 @@ public static IComparable ShouldBeGreaterThanOrEqualTo(this IComparable arg1, IC { throw NewException("Should be greater than or equal to {0} but is {1}", arg2, arg1); } - - return arg1; } - public static IComparable ShouldBeLessThan(this IComparable arg1, IComparable arg2) + public static void ShouldBeLessThan(this IComparable arg1, IComparable arg2) { if (arg2 == null) { @@ -63,11 +58,9 @@ public static IComparable ShouldBeLessThan(this IComparable arg1, IComparable ar { throw NewException("Should be less than {0} but is {1}", arg2, arg1); } - - return arg1; } - public static IComparable ShouldBeLessThanOrEqualTo(this IComparable arg1, IComparable arg2) + public static void ShouldBeLessThanOrEqualTo(this IComparable arg1, IComparable arg2) { if (arg2 == null) { @@ -83,8 +76,6 @@ public static IComparable ShouldBeLessThanOrEqualTo(this IComparable arg1, IComp { throw NewException("Should be less than or equal to {0} but is {1}", arg2, arg1); } - - return arg1; } private static object TryToChangeType(this object original, Type type) diff --git a/src/Machine.Specifications.Should/Comparers/AssertEqualityComparer.cs b/src/Machine.Specifications.Should/Comparers/AssertEqualityComparer.cs new file mode 100644 index 00000000..cc6caba7 --- /dev/null +++ b/src/Machine.Specifications.Should/Comparers/AssertEqualityComparer.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; + +namespace Machine.Specifications.Comparers +{ + internal class AssertEqualityComparer : IEqualityComparer + { + private static readonly IEqualityComparerStrategy[] Comparers = new IEqualityComparerStrategy[] + { + new EquatableComparer(), + new ComparableComparer(), + new EnumerableComparer(), + new GenericTypeComparer(), + new TypeComparer() + }; + + private static readonly DefaultComparer DefaultComparer = new DefaultComparer(); + + public static readonly AssertEqualityComparer Default = new AssertEqualityComparer(); + + public bool Equals(T x, T y) + { + foreach (var comparer in Comparers) + { + var result = comparer.Equals(x, y); + + if (result != null) + { + return result.Value; + } + } + + return DefaultComparer.Equals(x, y); + } + + public int GetHashCode(T obj) + { + return obj.GetHashCode(); + } + } +} diff --git a/src/Machine.Specifications.Should/Comparers/ComparableComparer.cs b/src/Machine.Specifications.Should/Comparers/ComparableComparer.cs index 9ac58e19..68cb19e4 100644 --- a/src/Machine.Specifications.Should/Comparers/ComparableComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/ComparableComparer.cs @@ -2,26 +2,26 @@ namespace Machine.Specifications.Comparers { - class ComparableComparer : IComparerStrategy + internal class ComparableComparer : IEqualityComparerStrategy { - public ComparisionResult Compare(T x, T y) + public bool? Equals(T x, T y) { if (x is IComparable comparable1) { - return new ComparisionResult(comparable1.CompareTo(y)); + return comparable1.CompareTo(y) == 0; } if (x is IComparable comparable2) { - if (!(comparable2.GetType().IsInstanceOfType(y))) + if (!comparable2.GetType().IsInstanceOfType(y)) { - return new NoResult(); + return null; } - return new ComparisionResult(comparable2.CompareTo(y)); + return comparable2.CompareTo(y) == 0; } - return new NoResult(); + return null; } } } diff --git a/src/Machine.Specifications.Should/Comparers/ComparerFactory.cs b/src/Machine.Specifications.Should/Comparers/ComparerFactory.cs deleted file mode 100644 index 5f212328..00000000 --- a/src/Machine.Specifications.Should/Comparers/ComparerFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; - -namespace Machine.Specifications.Comparers -{ - internal static class ComparerFactory - { - public static IEnumerable> GetComparers() - { - return new IComparerStrategy[] - { - new EnumerableComparer(), - new GenericTypeComparer(), - new ComparableComparer(), - new EquatableComparer(), - new TypeComparer() - }; - } - - public static IComparer GetDefaultComparer() - { - return new DefaultComparer(); - } - } -} diff --git a/src/Machine.Specifications.Should/Comparers/ComparisionResult.cs b/src/Machine.Specifications.Should/Comparers/ComparisionResult.cs deleted file mode 100644 index 8634ac0b..00000000 --- a/src/Machine.Specifications.Should/Comparers/ComparisionResult.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Machine.Specifications.Comparers -{ - internal class ComparisionResult - { - public ComparisionResult(int result) - { - FoundResult = true; - Result = result; - } - - protected ComparisionResult() - { - } - - public bool FoundResult { get; protected set; } - - public int Result { get; } - } -} diff --git a/src/Machine.Specifications.Should/Comparers/DefaultComparer.cs b/src/Machine.Specifications.Should/Comparers/DefaultComparer.cs index 5cc36cdc..ee095eb1 100644 --- a/src/Machine.Specifications.Should/Comparers/DefaultComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/DefaultComparer.cs @@ -2,14 +2,16 @@ namespace Machine.Specifications.Comparers { - internal class DefaultComparer : IComparer + internal class DefaultComparer : IEqualityComparer { - public int Compare(T x, T y) + public bool Equals(T x, T y) { - // Last case, rely on Object.Equals - return Equals(x, y) - ? 0 - : -1; + return object.Equals(x, y); + } + + public int GetHashCode(T obj) + { + return obj.GetHashCode(); } } } diff --git a/src/Machine.Specifications.Should/Comparers/EnumerableComparer.cs b/src/Machine.Specifications.Should/Comparers/EnumerableComparer.cs index 3afa430a..7a13926a 100644 --- a/src/Machine.Specifications.Should/Comparers/EnumerableComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/EnumerableComparer.cs @@ -2,9 +2,9 @@ namespace Machine.Specifications.Comparers { - internal class EnumerableComparer : IComparerStrategy + internal class EnumerableComparer : IEqualityComparerStrategy { - public ComparisionResult Compare(T x, T y) + public bool? Equals(T x, T y) { if (x is IEnumerable enumerableX && y is IEnumerable enumerableY) { @@ -18,17 +18,17 @@ public ComparisionResult Compare(T x, T y) if (!hasNextX || !hasNextY) { - return new ComparisionResult(hasNextX == hasNextY ? 0 : -1); + return hasNextX == hasNextY; } if (!Equals(enumeratorX.Current, enumeratorY.Current)) { - return new ComparisionResult(-1); + return false; } } } - return new NoResult(); + return null; } } } diff --git a/src/Machine.Specifications.Should/Comparers/EquatableComparer.cs b/src/Machine.Specifications.Should/Comparers/EquatableComparer.cs index 3068cc45..8c4a3dec 100644 --- a/src/Machine.Specifications.Should/Comparers/EquatableComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/EquatableComparer.cs @@ -2,16 +2,16 @@ namespace Machine.Specifications.Comparers { - internal class EquatableComparer : IComparerStrategy + internal class EquatableComparer : IEqualityComparerStrategy { - public ComparisionResult Compare(T x, T y) + public bool? Equals(T x, T y) { if (!(x is IEquatable equatable)) { - return new NoResult(); + return null; } - return new ComparisionResult(equatable.Equals(y) ? 0 : -1); + return equatable.Equals(y); } } } diff --git a/src/Machine.Specifications.Should/Comparers/GenericTypeComparer.cs b/src/Machine.Specifications.Should/Comparers/GenericTypeComparer.cs index fc3acefa..cb041304 100644 --- a/src/Machine.Specifications.Should/Comparers/GenericTypeComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/GenericTypeComparer.cs @@ -1,27 +1,32 @@ -using System.Reflection; +using System; namespace Machine.Specifications.Comparers { - internal class GenericTypeComparer : IComparerStrategy + internal class GenericTypeComparer : IEqualityComparerStrategy { - public ComparisionResult Compare(T x, T y) + public bool? Equals(T x, T y) { var type = typeof(T); - if (!type.GetTypeInfo().IsValueType || (type.GetTypeInfo().IsGenericType && type.IsNullable())) + if (!type.IsValueType || (type.IsGenericType && IsNullable(type))) { - if (x.IsEqualToDefault()) + if (object.Equals(x, default(T))) { - return new ComparisionResult(y.IsEqualToDefault() ? 0 : -1); + return object.Equals(y, default(T)); } - if (y.IsEqualToDefault()) + if (object.Equals(y, default(T))) { - return new ComparisionResult(-1); + return false; } } - return new NoResult(); + return null; + } + + private bool IsNullable(Type type) + { + return type.GetGenericTypeDefinition().IsAssignableFrom(typeof(Nullable<>)); } } } diff --git a/src/Machine.Specifications.Should/Comparers/IComparerStrategy.cs b/src/Machine.Specifications.Should/Comparers/IComparerStrategy.cs deleted file mode 100644 index 79f8757c..00000000 --- a/src/Machine.Specifications.Should/Comparers/IComparerStrategy.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Machine.Specifications.Comparers -{ - internal interface IComparerStrategy - { - ComparisionResult Compare(T x, T y); - } -} diff --git a/src/Machine.Specifications.Should/Comparers/IEqualityComparerStrategy.cs b/src/Machine.Specifications.Should/Comparers/IEqualityComparerStrategy.cs new file mode 100644 index 00000000..ad877ce4 --- /dev/null +++ b/src/Machine.Specifications.Should/Comparers/IEqualityComparerStrategy.cs @@ -0,0 +1,7 @@ +namespace Machine.Specifications.Comparers +{ + internal interface IEqualityComparerStrategy + { + bool? Equals(T x, T y); + } +} diff --git a/src/Machine.Specifications.Should/Comparers/NoResult.cs b/src/Machine.Specifications.Should/Comparers/NoResult.cs deleted file mode 100644 index 0abe64af..00000000 --- a/src/Machine.Specifications.Should/Comparers/NoResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Machine.Specifications.Comparers -{ - internal class NoResult : ComparisionResult - { - public NoResult() - { - FoundResult = false; - } - } -} diff --git a/src/Machine.Specifications.Should/Comparers/ObjectExtension.cs b/src/Machine.Specifications.Should/Comparers/ObjectExtension.cs deleted file mode 100644 index f630d2fa..00000000 --- a/src/Machine.Specifications.Should/Comparers/ObjectExtension.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Machine.Specifications.Comparers -{ - internal static class ObjectExtension - { - public static bool IsEqualToDefault(this T obj) - { - return Equals(obj, default(T)); - } - } -} diff --git a/src/Machine.Specifications.Should/Comparers/TypeComparer.cs b/src/Machine.Specifications.Should/Comparers/TypeComparer.cs index f1ff4d8a..60f8b23c 100644 --- a/src/Machine.Specifications.Should/Comparers/TypeComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/TypeComparer.cs @@ -1,15 +1,15 @@ namespace Machine.Specifications.Comparers { - internal class TypeComparer : IComparerStrategy + internal class TypeComparer : IEqualityComparerStrategy { - public ComparisionResult Compare(T x, T y) + public bool? Equals(T x, T y) { if (x.GetType() != y.GetType()) { - return new ComparisionResult(-1); + return false; } - return new NoResult(); + return null; } } } diff --git a/src/Machine.Specifications.Should/Comparers/TypeExtension.cs b/src/Machine.Specifications.Should/Comparers/TypeExtension.cs deleted file mode 100644 index 68881052..00000000 --- a/src/Machine.Specifications.Should/Comparers/TypeExtension.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Machine.Specifications.Comparers -{ - internal static class TypeExtension - { - public static bool IsNullable(this Type type) - { - return type.GetGenericTypeDefinition().IsAssignableFrom(typeof(Nullable<>)); - } - } -} diff --git a/src/Machine.Specifications.Should/DateTimeExtensions.cs b/src/Machine.Specifications.Should/DateTimeExtensions.cs index 99e514fa..30a7f26d 100644 --- a/src/Machine.Specifications.Should/DateTimeExtensions.cs +++ b/src/Machine.Specifications.Should/DateTimeExtensions.cs @@ -1,5 +1,5 @@ using System; -using Machine.Specifications.Text; +using Machine.Specifications.Formatting; namespace Machine.Specifications { diff --git a/src/Machine.Specifications.Should/EnumerableExtensions.cs b/src/Machine.Specifications.Should/EnumerableExtensions.cs index 238237d1..7aa445c7 100644 --- a/src/Machine.Specifications.Should/EnumerableExtensions.cs +++ b/src/Machine.Specifications.Should/EnumerableExtensions.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using Machine.Specifications.Text; -using Machine.Specifications.Utility.Internal; +using Machine.Specifications.Comparers; +using Machine.Specifications.Formatting; namespace Machine.Specifications { @@ -42,13 +42,11 @@ public static void ShouldContain(this IEnumerable list, params T[] items) public static void ShouldContain(this IEnumerable list, IEnumerable items) { - var comparer = new AssertComparer(); - var listArray = list.ToArray(); var itemsArray = items.ToArray(); var noContain = itemsArray - .Where(x => !listArray.Contains(x, comparer)) + .Where(x => !listArray.Contains(x, AssertEqualityComparer.Default)) .ToList(); if (noContain.Any()) @@ -93,13 +91,11 @@ public static void ShouldNotContain(this IEnumerable list, params T[] item public static void ShouldNotContain(this IEnumerable list, IEnumerable items) { - var comparer = new AssertComparer(); - var listArray = list.ToArray(); var itemsArray = items.ToArray(); var contains = itemsArray - .Where(x => listArray.Contains(x, comparer)) + .Where(x => listArray.Contains(x, AssertEqualityComparer.Default)) .ToList(); if (contains.Any()) @@ -163,11 +159,10 @@ public static void ShouldContainOnly(this IEnumerable list, IEnumerable var source = new List(listArray); var noContain = new List(); - var comparer = new AssertComparer(); foreach (var item in itemsArray) { - if (!source.Contains(item, comparer)) + if (!source.Contains(item, AssertEqualityComparer.Default)) { noContain.Add(item); } diff --git a/src/Machine.Specifications.Should/EqualityExtensions.cs b/src/Machine.Specifications.Should/EqualityExtensions.cs new file mode 100644 index 00000000..b83082c9 --- /dev/null +++ b/src/Machine.Specifications.Should/EqualityExtensions.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq.Expressions; +using Machine.Specifications.Comparers; +using Machine.Specifications.Formatting; +using Machine.Specifications.Utility.Internal; + +namespace Machine.Specifications +{ + public static class EqualityExtensions + { + public static void ShouldEqual(this T actual, T expected) + { + if (!AssertEqualityComparer.Default.Equals(actual, expected)) + { + throw new SpecificationException(PrettyPrintingExtensions.FormatErrorMessage(actual, expected)); + } + } + + public static void ShouldNotEqual(this T actual, T expected) + { + if (AssertEqualityComparer.Default.Equals(actual, expected)) + { + throw new SpecificationException($"Should not equal {expected.ToUsefulString()} but does: {actual.ToUsefulString()}"); + } + } + + public static void ShouldMatch(this T actual, Expression> condition) + { + var matches = condition.Compile().Invoke(actual); + + if (matches) + { + return; + } + + throw new SpecificationException($"Should match expression [{condition}], but does not."); + } + + public static void ShouldBeTheSameAs(this object actual, object expected) + { + if (!ReferenceEquals(actual, expected)) + { + throw new SpecificationException($"Should be the same as {expected} but is {actual}"); + } + } + + public static void ShouldNotBeTheSameAs(this object actual, object expected) + { + if (ReferenceEquals(actual, expected)) + { + throw new SpecificationException($"Should not be the same as {expected} but is {actual}"); + } + } + } +} diff --git a/src/Machine.Specifications.Should/ObjectExtensions.cs b/src/Machine.Specifications.Should/EquivalenceExtensions.cs similarity index 62% rename from src/Machine.Specifications.Should/ObjectExtensions.cs rename to src/Machine.Specifications.Should/EquivalenceExtensions.cs index 45fe3e26..dd87165f 100644 --- a/src/Machine.Specifications.Should/ObjectExtensions.cs +++ b/src/Machine.Specifications.Should/EquivalenceExtensions.cs @@ -2,125 +2,14 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using JetBrains.Annotations; +using Machine.Specifications.Formatting; using Machine.Specifications.Reflection; -using Machine.Specifications.Text; using Machine.Specifications.Utility.Internal; namespace Machine.Specifications { - public static class ObjectExtensions + public static class EquivalenceExtensions { - [AssertionMethod] - public static void ShouldBeNull([AssertionCondition(AssertionConditionType.IS_NULL)] this object anObject) - { - if (anObject != null) - { - throw new SpecificationException($"Should be [null] but is {anObject.ToUsefulString()}"); - } - } - - [AssertionMethod] - public static void ShouldNotBeNull([AssertionCondition(AssertionConditionType.IS_NOT_NULL)] this object anObject) - { - if (anObject == null) - { - throw new SpecificationException("Should be [not null] but is [null]"); - } - } - - public static object ShouldBeTheSameAs(this object actual, object expected) - { - if (!ReferenceEquals(actual, expected)) - { - throw new SpecificationException($"Should be the same as {expected} but is {actual}"); - } - - return expected; - } - - public static object ShouldNotBeTheSameAs(this object actual, object expected) - { - if (ReferenceEquals(actual, expected)) - { - throw new SpecificationException($"Should not be the same as {expected} but is {actual}"); - } - - return expected; - } - - public static void ShouldBeOfExactType(this object actual, Type expected) - { - if (actual == null) - { - throw new SpecificationException($"Should be of type {expected} but is [null]"); - } - - if (actual.GetType() != expected) - { - throw new SpecificationException($"Should be of type {expected} but is of type {actual.GetType()}"); - } - } - - public static void ShouldNotBeOfExactType(this object actual, Type expected) - { - if (actual == null) - { - throw new SpecificationException($"Should not be of type {expected} but is [null]"); - } - - if (actual.GetType() == expected) - { - throw new SpecificationException($"Should not be of type {expected} but is of type {actual.GetType()}"); - } - } - - public static void ShouldBeOfExactType(this object actual) - { - actual.ShouldBeOfExactType(typeof(T)); - } - - public static void ShouldNotBeOfExactType(this object actual) - { - actual.ShouldNotBeOfExactType(typeof(T)); - } - - public static void ShouldBeAssignableTo(this object actual, Type expected) - { - if (actual == null) - { - throw new SpecificationException($"Should be assignable to type {expected} but is [null]"); - } - - if (!expected.IsInstanceOfType(actual)) - { - throw new SpecificationException($"Should be assignable to type {expected} but is not. Actual type is {actual.GetType()}"); - } - } - - public static void ShouldNotBeAssignableTo(this object actual, Type expected) - { - if (actual == null) - { - throw new SpecificationException($"Should not be assignable to type {expected} but is [null]"); - } - - if (expected.IsInstanceOfType(actual)) - { - throw new SpecificationException($"Should not be assignable to type {expected} but is. Actual type is {actual.GetType()}"); - } - } - - public static void ShouldBeAssignableTo(this object actual) - { - actual.ShouldBeAssignableTo(typeof(T)); - } - - public static void ShouldNotBeAssignableTo(this object actual) - { - actual.ShouldNotBeAssignableTo(typeof(T)); - } - public static void ShouldBeLike(this object obj, object expected) { var exceptions = ShouldBeLikeInternal(obj, expected, string.Empty, new HashSet()).ToArray(); diff --git a/src/Machine.Specifications.Should/FloatingPointExtensions.cs b/src/Machine.Specifications.Should/FloatingPointExtensions.cs index 8ff8e704..de681b64 100644 --- a/src/Machine.Specifications.Should/FloatingPointExtensions.cs +++ b/src/Machine.Specifications.Should/FloatingPointExtensions.cs @@ -1,5 +1,5 @@ using System; -using Machine.Specifications.Text; +using Machine.Specifications.Formatting; namespace Machine.Specifications { diff --git a/src/Machine.Specifications.Should/Text/EnumerableExtensions.cs b/src/Machine.Specifications.Should/Formatting/EnumerableExtensions.cs similarity index 96% rename from src/Machine.Specifications.Should/Text/EnumerableExtensions.cs rename to src/Machine.Specifications.Should/Formatting/EnumerableExtensions.cs index 807fc3a6..9d5503f8 100644 --- a/src/Machine.Specifications.Should/Text/EnumerableExtensions.cs +++ b/src/Machine.Specifications.Should/Formatting/EnumerableExtensions.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Text; -namespace Machine.Specifications.Text +namespace Machine.Specifications.Formatting { internal static class EnumerableExtensions { diff --git a/src/Machine.Specifications.Should/Text/ObjectExtensions.cs b/src/Machine.Specifications.Should/Formatting/ObjectExtensions.cs similarity index 96% rename from src/Machine.Specifications.Should/Text/ObjectExtensions.cs rename to src/Machine.Specifications.Should/Formatting/ObjectExtensions.cs index e9a46765..877d23af 100644 --- a/src/Machine.Specifications.Should/Text/ObjectExtensions.cs +++ b/src/Machine.Specifications.Should/Formatting/ObjectExtensions.cs @@ -2,7 +2,7 @@ using System.Linq; using System.Reflection; -namespace Machine.Specifications.Text +namespace Machine.Specifications.Formatting { internal static class ObjectExtensions { diff --git a/src/Machine.Specifications.Should/Formatting/ObjectFormatter.cs b/src/Machine.Specifications.Should/Formatting/ObjectFormatter.cs new file mode 100644 index 00000000..8f25bd2f --- /dev/null +++ b/src/Machine.Specifications.Should/Formatting/ObjectFormatter.cs @@ -0,0 +1,61 @@ +using System.Collections; +using System.Linq; + +namespace Machine.Specifications.Formatting +{ + internal static class ObjectFormatter + { + public static string Format(object value) + { + if (value == null) + { + return "[null]"; + } + + if (value is string valueAsString) + { + return $@"""{valueAsString.Replace("\n", "\\n")}"""; + } + + var type = value.GetType(); + + if (type.IsValueType) + { + return "[" + value + "]"; + } + + if (value is IEnumerable items) + { + var enumerable = items.Cast(); + + return type + ":\r\n" + enumerable.EachToUsefulString(); + } + + var stringValue = value.ToString(); + + if (stringValue == null || stringValue.Trim() == string.Empty) + { + return $"{type}:[]"; + } + + stringValue = stringValue.Trim(); + + if (stringValue.Contains("\n")) + { + return string.Format(@"{1}: +[ +{0} +]", stringValue.Indent(), type); + } + + var typeString = type.ToString(); + + if (typeString == stringValue) + { + return typeString; + } + + return $"{type}:[{stringValue}]"; + } + } +} diff --git a/src/Machine.Specifications.Should/Text/StringExtensions.cs b/src/Machine.Specifications.Should/Formatting/StringExtensions.cs similarity index 96% rename from src/Machine.Specifications.Should/Text/StringExtensions.cs rename to src/Machine.Specifications.Should/Formatting/StringExtensions.cs index 22fdf77a..8d4558d8 100644 --- a/src/Machine.Specifications.Should/Text/StringExtensions.cs +++ b/src/Machine.Specifications.Should/Formatting/StringExtensions.cs @@ -3,7 +3,7 @@ using System.Text; using System.Text.RegularExpressions; -namespace Machine.Specifications.Text +namespace Machine.Specifications.Formatting { internal static class StringExtensions { diff --git a/src/Machine.Specifications.Should/NullExtensions.cs b/src/Machine.Specifications.Should/NullExtensions.cs new file mode 100644 index 00000000..0ebab00b --- /dev/null +++ b/src/Machine.Specifications.Should/NullExtensions.cs @@ -0,0 +1,26 @@ +using JetBrains.Annotations; +using Machine.Specifications.Formatting; + +namespace Machine.Specifications +{ + public static class NullExtensions + { + [AssertionMethod] + public static void ShouldBeNull([AssertionCondition(AssertionConditionType.IS_NULL)] this object anObject) + { + if (anObject != null) + { + throw new SpecificationException($"Should be [null] but is {anObject.ToUsefulString()}"); + } + } + + [AssertionMethod] + public static void ShouldNotBeNull([AssertionCondition(AssertionConditionType.IS_NOT_NULL)] this object anObject) + { + if (anObject == null) + { + throw new SpecificationException("Should be [not null] but is [null]"); + } + } + } +} diff --git a/src/Machine.Specifications.Should/ShouldExtensionMethods.cs b/src/Machine.Specifications.Should/ShouldExtensionMethods.cs deleted file mode 100644 index cdb24236..00000000 --- a/src/Machine.Specifications.Should/ShouldExtensionMethods.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using Machine.Specifications.Text; -using Machine.Specifications.Utility.Internal; - -namespace Machine.Specifications -{ - public static class ShouldExtensionMethods - { - private static bool SafeEquals(this T left, T right) - { - var comparer = new AssertComparer(); - - return comparer.Compare(left, right) == 0; - } - - public static T ShouldEqual(this T actual, T expected) - { - if (!actual.SafeEquals(expected)) - { - throw new SpecificationException(PrettyPrintingExtensions.FormatErrorMessage(actual, expected)); - } - - return actual; - } - - public static object ShouldNotEqual(this T actual, T expected) - { - if (actual.SafeEquals(expected)) - { - throw new SpecificationException($"Should not equal {expected.ToUsefulString()} but does: {actual.ToUsefulString()}"); - } - - return actual; - } - - public static void ShouldMatch(this T actual, Expression> condition) - { - var matches = condition.Compile().Invoke(actual); - - if (matches) - { - return; - } - - throw new SpecificationException($"Should match expression [{condition}], but does not."); - } - - private static SpecificationException NewException(string message, params object[] parameters) - { - if (parameters.Any()) - { - return new SpecificationException(string.Format(message.EnsureSafeFormat(), parameters.Select(x => x.ToUsefulString()).Cast().ToArray())); - } - - return new SpecificationException(message); - } - } -} diff --git a/src/Machine.Specifications.Should/StringExtensions.cs b/src/Machine.Specifications.Should/StringExtensions.cs index 3c7c19c6..d1ec4d69 100644 --- a/src/Machine.Specifications.Should/StringExtensions.cs +++ b/src/Machine.Specifications.Should/StringExtensions.cs @@ -2,28 +2,28 @@ using System.Globalization; using System.Linq; using System.Text.RegularExpressions; -using Machine.Specifications.Text; +using Machine.Specifications.Formatting; namespace Machine.Specifications { public static class StringExtensions { - public static void ShouldBeEmpty(this string aString) + public static void ShouldBeEmpty(this string value) { - if (aString == null) + if (value == null) { throw new SpecificationException("Should be empty but is [null]"); } - if (!string.IsNullOrEmpty(aString)) + if (!string.IsNullOrEmpty(value)) { - throw NewException("Should be empty but is {0}", aString); + throw NewException("Should be empty but is {0}", value); } } - public static void ShouldNotBeEmpty(this string aString) + public static void ShouldNotBeEmpty(this string value) { - if (string.IsNullOrEmpty(aString)) + if (string.IsNullOrEmpty(value)) { throw NewException("Should not be empty but is"); } diff --git a/src/Machine.Specifications.Should/TypeExtensions.cs b/src/Machine.Specifications.Should/TypeExtensions.cs new file mode 100644 index 00000000..6b59b609 --- /dev/null +++ b/src/Machine.Specifications.Should/TypeExtensions.cs @@ -0,0 +1,79 @@ +using System; + +namespace Machine.Specifications +{ + public static class TypeExtensions + { + public static void ShouldBeOfExactType(this object actual, Type expected) + { + if (actual == null) + { + throw new SpecificationException($"Should be of type {expected} but is [null]"); + } + + if (actual.GetType() != expected) + { + throw new SpecificationException($"Should be of type {expected} but is of type {actual.GetType()}"); + } + } + + public static void ShouldNotBeOfExactType(this object actual, Type expected) + { + if (actual == null) + { + throw new SpecificationException($"Should not be of type {expected} but is [null]"); + } + + if (actual.GetType() == expected) + { + throw new SpecificationException($"Should not be of type {expected} but is of type {actual.GetType()}"); + } + } + + public static void ShouldBeOfExactType(this object actual) + { + actual.ShouldBeOfExactType(typeof(T)); + } + + public static void ShouldNotBeOfExactType(this object actual) + { + actual.ShouldNotBeOfExactType(typeof(T)); + } + + public static void ShouldBeAssignableTo(this object actual, Type expected) + { + if (actual == null) + { + throw new SpecificationException($"Should be assignable to type {expected} but is [null]"); + } + + if (!expected.IsInstanceOfType(actual)) + { + throw new SpecificationException($"Should be assignable to type {expected} but is not. Actual type is {actual.GetType()}"); + } + } + + public static void ShouldNotBeAssignableTo(this object actual, Type expected) + { + if (actual == null) + { + throw new SpecificationException($"Should not be assignable to type {expected} but is [null]"); + } + + if (expected.IsInstanceOfType(actual)) + { + throw new SpecificationException($"Should not be assignable to type {expected} but is. Actual type is {actual.GetType()}"); + } + } + + public static void ShouldBeAssignableTo(this object actual) + { + actual.ShouldBeAssignableTo(typeof(T)); + } + + public static void ShouldNotBeAssignableTo(this object actual) + { + actual.ShouldNotBeAssignableTo(typeof(T)); + } + } +} diff --git a/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs b/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs index 388971de..b36ff821 100644 --- a/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs +++ b/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs @@ -1,5 +1,5 @@ using System; -using Machine.Specifications.Text; +using Machine.Specifications.Formatting; namespace Machine.Specifications.Utility.Internal { @@ -92,7 +92,7 @@ private static void GetStringsAroundFirstDifference(string expected, string actu private static bool IsInCopyFrameLength(int start, int end, int max) { - int length = end - start; + var length = end - start; if (start > 0) { From bddfe3238828b068bd6217b333a707f88c56d697 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Tue, 7 Dec 2021 16:28:31 +0800 Subject: [PATCH 4/9] comparers --- .../Comparers/AssertEqualityComparer.cs | 30 +++++++++------- .../Comparers/ComparableComparer.cs | 27 -------------- .../Comparers/ComparableStrategy.cs | 35 +++++++++++++++++++ .../Comparers/DefaultComparer.cs | 17 --------- ...rableComparer.cs => EnumerableStrategy.cs} | 4 +-- .../Comparers/EquatableComparer.cs | 17 --------- .../Comparers/EquatableStrategy.cs | 29 +++++++++++++++ ...TypeComparer.cs => GenericTypeStrategy.cs} | 10 +++--- .../Comparers/IEqualityComparerStrategy.cs | 7 ---- .../Comparers/IEqualityStrategy.cs | 7 ++++ .../Comparers/NullStrategy.cs | 20 +++++++++++ .../{TypeComparer.cs => TypeStrategy.cs} | 9 +++-- .../Machine.Specifications.Should.csproj | 2 ++ 13 files changed, 124 insertions(+), 90 deletions(-) delete mode 100644 src/Machine.Specifications.Should/Comparers/ComparableComparer.cs create mode 100644 src/Machine.Specifications.Should/Comparers/ComparableStrategy.cs delete mode 100644 src/Machine.Specifications.Should/Comparers/DefaultComparer.cs rename src/Machine.Specifications.Should/Comparers/{EnumerableComparer.cs => EnumerableStrategy.cs} (88%) delete mode 100644 src/Machine.Specifications.Should/Comparers/EquatableComparer.cs create mode 100644 src/Machine.Specifications.Should/Comparers/EquatableStrategy.cs rename src/Machine.Specifications.Should/Comparers/{GenericTypeComparer.cs => GenericTypeStrategy.cs} (58%) delete mode 100644 src/Machine.Specifications.Should/Comparers/IEqualityComparerStrategy.cs create mode 100644 src/Machine.Specifications.Should/Comparers/IEqualityStrategy.cs create mode 100644 src/Machine.Specifications.Should/Comparers/NullStrategy.cs rename src/Machine.Specifications.Should/Comparers/{TypeComparer.cs => TypeStrategy.cs} (51%) diff --git a/src/Machine.Specifications.Should/Comparers/AssertEqualityComparer.cs b/src/Machine.Specifications.Should/Comparers/AssertEqualityComparer.cs index cc6caba7..dd680142 100644 --- a/src/Machine.Specifications.Should/Comparers/AssertEqualityComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/AssertEqualityComparer.cs @@ -4,24 +4,23 @@ namespace Machine.Specifications.Comparers { internal class AssertEqualityComparer : IEqualityComparer { - private static readonly IEqualityComparerStrategy[] Comparers = new IEqualityComparerStrategy[] + private static IEqualityStrategy[] Strategies = new IEqualityStrategy[] { - new EquatableComparer(), - new ComparableComparer(), - new EnumerableComparer(), - new GenericTypeComparer(), - new TypeComparer() + new NullStrategy(), + new EquatableStrategy(), + new ComparableStrategy(), + new EnumerableStrategy(), + new TypeStrategy(), + new GenericTypeStrategy() }; - private static readonly DefaultComparer DefaultComparer = new DefaultComparer(); - public static readonly AssertEqualityComparer Default = new AssertEqualityComparer(); - public bool Equals(T x, T y) + public bool Equals(T? x, T? y) { - foreach (var comparer in Comparers) + foreach (var strategy in Strategies) { - var result = comparer.Equals(x, y); + var result = strategy.Equals(x, y); if (result != null) { @@ -29,11 +28,16 @@ public bool Equals(T x, T y) } } - return DefaultComparer.Equals(x, y); + return object.Equals(x, y); } - public int GetHashCode(T obj) + public int GetHashCode(T? obj) { + if (obj == null) + { + return 0; + } + return obj.GetHashCode(); } } diff --git a/src/Machine.Specifications.Should/Comparers/ComparableComparer.cs b/src/Machine.Specifications.Should/Comparers/ComparableComparer.cs deleted file mode 100644 index 68cb19e4..00000000 --- a/src/Machine.Specifications.Should/Comparers/ComparableComparer.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -namespace Machine.Specifications.Comparers -{ - internal class ComparableComparer : IEqualityComparerStrategy - { - public bool? Equals(T x, T y) - { - if (x is IComparable comparable1) - { - return comparable1.CompareTo(y) == 0; - } - - if (x is IComparable comparable2) - { - if (!comparable2.GetType().IsInstanceOfType(y)) - { - return null; - } - - return comparable2.CompareTo(y) == 0; - } - - return null; - } - } -} diff --git a/src/Machine.Specifications.Should/Comparers/ComparableStrategy.cs b/src/Machine.Specifications.Should/Comparers/ComparableStrategy.cs new file mode 100644 index 00000000..3678a444 --- /dev/null +++ b/src/Machine.Specifications.Should/Comparers/ComparableStrategy.cs @@ -0,0 +1,35 @@ +using System; + +namespace Machine.Specifications.Comparers +{ + internal class ComparableStrategy : IEqualityStrategy + { + public bool? Equals(T? x, T? y) + { + if (x is IComparable genericComparable) + { + try + { + return genericComparable.CompareTo(y!) == 0; + } + catch (Exception) + { + return false; + } + } + + if (x is IComparable comparable) + { + try + { + return comparable.CompareTo(y!) == 0; + } + catch + { + } + } + + return null; + } + } +} diff --git a/src/Machine.Specifications.Should/Comparers/DefaultComparer.cs b/src/Machine.Specifications.Should/Comparers/DefaultComparer.cs deleted file mode 100644 index ee095eb1..00000000 --- a/src/Machine.Specifications.Should/Comparers/DefaultComparer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; - -namespace Machine.Specifications.Comparers -{ - internal class DefaultComparer : IEqualityComparer - { - public bool Equals(T x, T y) - { - return object.Equals(x, y); - } - - public int GetHashCode(T obj) - { - return obj.GetHashCode(); - } - } -} diff --git a/src/Machine.Specifications.Should/Comparers/EnumerableComparer.cs b/src/Machine.Specifications.Should/Comparers/EnumerableStrategy.cs similarity index 88% rename from src/Machine.Specifications.Should/Comparers/EnumerableComparer.cs rename to src/Machine.Specifications.Should/Comparers/EnumerableStrategy.cs index 7a13926a..5960d949 100644 --- a/src/Machine.Specifications.Should/Comparers/EnumerableComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/EnumerableStrategy.cs @@ -2,9 +2,9 @@ namespace Machine.Specifications.Comparers { - internal class EnumerableComparer : IEqualityComparerStrategy + internal class EnumerableStrategy : IEqualityStrategy { - public bool? Equals(T x, T y) + public bool? Equals(T? x, T? y) { if (x is IEnumerable enumerableX && y is IEnumerable enumerableY) { diff --git a/src/Machine.Specifications.Should/Comparers/EquatableComparer.cs b/src/Machine.Specifications.Should/Comparers/EquatableComparer.cs deleted file mode 100644 index 8c4a3dec..00000000 --- a/src/Machine.Specifications.Should/Comparers/EquatableComparer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Machine.Specifications.Comparers -{ - internal class EquatableComparer : IEqualityComparerStrategy - { - public bool? Equals(T x, T y) - { - if (!(x is IEquatable equatable)) - { - return null; - } - - return equatable.Equals(y); - } - } -} diff --git a/src/Machine.Specifications.Should/Comparers/EquatableStrategy.cs b/src/Machine.Specifications.Should/Comparers/EquatableStrategy.cs new file mode 100644 index 00000000..0edb8585 --- /dev/null +++ b/src/Machine.Specifications.Should/Comparers/EquatableStrategy.cs @@ -0,0 +1,29 @@ +using System; + +namespace Machine.Specifications.Comparers +{ + internal class EquatableStrategy : IEqualityStrategy + { + public bool? Equals(T? x, T? y) + { + if (x is not IEquatable equatable) + { + return null; + } + + if (y == null) + { + return false; + } + + try + { + return equatable.Equals(y); + } + catch (Exception) + { + return null; + } + } + } +} diff --git a/src/Machine.Specifications.Should/Comparers/GenericTypeComparer.cs b/src/Machine.Specifications.Should/Comparers/GenericTypeStrategy.cs similarity index 58% rename from src/Machine.Specifications.Should/Comparers/GenericTypeComparer.cs rename to src/Machine.Specifications.Should/Comparers/GenericTypeStrategy.cs index cb041304..0d3acf01 100644 --- a/src/Machine.Specifications.Should/Comparers/GenericTypeComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/GenericTypeStrategy.cs @@ -2,13 +2,13 @@ namespace Machine.Specifications.Comparers { - internal class GenericTypeComparer : IEqualityComparerStrategy + internal class GenericTypeStrategy : IEqualityStrategy { - public bool? Equals(T x, T y) + public bool? Equals(T? x, T? y) { var type = typeof(T); - if (!type.IsValueType || (type.IsGenericType && IsNullable(type))) + if (!type.IsValueType || IsNullableGeneric(type)) { if (object.Equals(x, default(T))) { @@ -24,9 +24,9 @@ internal class GenericTypeComparer : IEqualityComparerStrategy return null; } - private bool IsNullable(Type type) + private bool IsNullableGeneric(Type type) { - return type.GetGenericTypeDefinition().IsAssignableFrom(typeof(Nullable<>)); + return type.IsGenericType && type.GetGenericTypeDefinition().IsAssignableFrom(typeof(Nullable<>)); } } } diff --git a/src/Machine.Specifications.Should/Comparers/IEqualityComparerStrategy.cs b/src/Machine.Specifications.Should/Comparers/IEqualityComparerStrategy.cs deleted file mode 100644 index ad877ce4..00000000 --- a/src/Machine.Specifications.Should/Comparers/IEqualityComparerStrategy.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Machine.Specifications.Comparers -{ - internal interface IEqualityComparerStrategy - { - bool? Equals(T x, T y); - } -} diff --git a/src/Machine.Specifications.Should/Comparers/IEqualityStrategy.cs b/src/Machine.Specifications.Should/Comparers/IEqualityStrategy.cs new file mode 100644 index 00000000..a94b8b2b --- /dev/null +++ b/src/Machine.Specifications.Should/Comparers/IEqualityStrategy.cs @@ -0,0 +1,7 @@ +namespace Machine.Specifications.Comparers +{ + internal interface IEqualityStrategy + { + bool? Equals(T? x, T? y); + } +} diff --git a/src/Machine.Specifications.Should/Comparers/NullStrategy.cs b/src/Machine.Specifications.Should/Comparers/NullStrategy.cs new file mode 100644 index 00000000..ff2627e9 --- /dev/null +++ b/src/Machine.Specifications.Should/Comparers/NullStrategy.cs @@ -0,0 +1,20 @@ +namespace Machine.Specifications.Comparers +{ + internal class NullStrategy : IEqualityStrategy + { + public bool? Equals(T? x, T? y) + { + if (x == null && y == null) + { + return true; + } + + if (x == null || y == null) + { + return false; + } + + return null; + } + } +} diff --git a/src/Machine.Specifications.Should/Comparers/TypeComparer.cs b/src/Machine.Specifications.Should/Comparers/TypeStrategy.cs similarity index 51% rename from src/Machine.Specifications.Should/Comparers/TypeComparer.cs rename to src/Machine.Specifications.Should/Comparers/TypeStrategy.cs index 60f8b23c..1ac9592d 100644 --- a/src/Machine.Specifications.Should/Comparers/TypeComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/TypeStrategy.cs @@ -1,9 +1,14 @@ namespace Machine.Specifications.Comparers { - internal class TypeComparer : IEqualityComparerStrategy + internal class TypeStrategy : IEqualityStrategy { - public bool? Equals(T x, T y) + public bool? Equals(T? x, T? y) { + if (x == null || y == null) + { + return null; + } + if (x.GetType() != y.GetType()) { return false; diff --git a/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj b/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj index 66ab3eca..68392c12 100644 --- a/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj +++ b/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj @@ -3,6 +3,8 @@ netstandard2.0 Machine.Specifications + enable + latest Assertion library for Machine.Specifications Machine Specifications From 3e1b103c40d7d10fc54a84b68b054132fd387799 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Wed, 8 Dec 2021 07:33:00 +0800 Subject: [PATCH 5/9] fix --- src/Machine.Specifications/Machine.Specifications.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Machine.Specifications/Machine.Specifications.csproj b/src/Machine.Specifications/Machine.Specifications.csproj index 29ba8768..8ed5bc2e 100644 --- a/src/Machine.Specifications/Machine.Specifications.csproj +++ b/src/Machine.Specifications/Machine.Specifications.csproj @@ -2,6 +2,7 @@ net472;netstandard2.0 + Machine.Specifications.Container Machine.Specifications is a Context/Specification framework geared towards removing language noise and simplifying tests Machine Specifications From b756ad68ef2903c24a7731b0c9c54699e03aa8e9 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Sat, 26 Feb 2022 22:41:51 +0800 Subject: [PATCH 6/9] fix --- .../Machine.Specifications.Should.Specs.csproj | 1 - src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Machine.Specifications.Should.Specs/Machine.Specifications.Should.Specs.csproj b/src/Machine.Specifications.Should.Specs/Machine.Specifications.Should.Specs.csproj index f1f90c72..cf479684 100644 --- a/src/Machine.Specifications.Should.Specs/Machine.Specifications.Should.Specs.csproj +++ b/src/Machine.Specifications.Should.Specs/Machine.Specifications.Should.Specs.csproj @@ -8,7 +8,6 @@ - diff --git a/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs b/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs index a1ad5fab..63432331 100644 --- a/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs +++ b/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Xunit; namespace Machine.Specifications.Should.Specs { From 2ea9110ba1f4044922406102398c1cab7f8a8cc1 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Mon, 28 Feb 2022 07:03:30 +0800 Subject: [PATCH 7/9] refactor --- .../ObjectExtensionsSpecs.cs} | 0 .../Reflection/ObjectGraphSpecs.cs | 10 +- .../ShouldBeLikeSpecs.cs | 2 +- .../ComparableExtensions.cs | 30 ++-- .../Comparers/AssertEqualityComparer.cs | 4 +- .../Comparers/IEqualityStrategy.cs | 2 +- .../DateTimeExtensions.cs | 4 +- .../EnumerableExtensions.cs | 14 +- .../EqualityExtensions.cs | 5 +- .../EquivalenceExtensions.cs | 83 +++++----- .../ExceptionExtensions.cs | 24 --- .../Exceptions.cs | 28 ++++ .../FloatingPointExtensions.cs | 6 +- .../Formatting/EnumerableExtensions.cs | 7 +- .../Formatting/ObjectExtensions.cs | 152 ++++++++++++++++-- .../Formatting/ObjectFormatter.cs | 61 ------- .../Formatting/StringExtensions.cs | 10 -- .../NullExtensions.cs | 10 +- .../Reflection/KeyValueNode.cs | 11 +- .../Reflection/LiteralNode.cs | 7 +- .../Reflection/Member.cs | 10 +- .../Reflection/ObjectGraph.cs | 33 +--- .../Reflection/SequenceNode.cs | 8 +- .../StringExtensions.cs | 62 +++---- .../TypeExtensions.cs | 42 ++--- .../Internal/PrettyPrintingExtensions.cs | 133 --------------- 26 files changed, 313 insertions(+), 445 deletions(-) rename src/Machine.Specifications.Should.Specs/{Utility/Internal/ErrorMessageSpecs.cs => Formatting/ObjectExtensionsSpecs.cs} (100%) create mode 100644 src/Machine.Specifications.Should/Exceptions.cs delete mode 100644 src/Machine.Specifications.Should/Formatting/ObjectFormatter.cs delete mode 100644 src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs diff --git a/src/Machine.Specifications.Should.Specs/Utility/Internal/ErrorMessageSpecs.cs b/src/Machine.Specifications.Should.Specs/Formatting/ObjectExtensionsSpecs.cs similarity index 100% rename from src/Machine.Specifications.Should.Specs/Utility/Internal/ErrorMessageSpecs.cs rename to src/Machine.Specifications.Should.Specs/Formatting/ObjectExtensionsSpecs.cs diff --git a/src/Machine.Specifications.Should.Specs/Reflection/ObjectGraphSpecs.cs b/src/Machine.Specifications.Should.Specs/Reflection/ObjectGraphSpecs.cs index 7cac5e23..d33b9b9f 100644 --- a/src/Machine.Specifications.Should.Specs/Reflection/ObjectGraphSpecs.cs +++ b/src/Machine.Specifications.Should.Specs/Reflection/ObjectGraphSpecs.cs @@ -26,7 +26,7 @@ class with_property ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Property").ShouldNotBeEmpty(); It should_retrieve_value = () => - ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Property").Single().ValueGetter().ShouldEqual("value"); + ((KeyValueNode) result).KeyValues.Single(m => m.Name == "Property").ValueGetter().ShouldEqual("value"); It should_return_a_key_value_node = () => result.ShouldBeOfExactType(); @@ -48,7 +48,7 @@ class with_field ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Field").ShouldNotBeEmpty(); It should_retrieve_value = () => - ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Field").Single().ValueGetter().ShouldEqual("value"); + ((KeyValueNode) result).KeyValues.Single(m => m.Name == "Field").ValueGetter().ShouldEqual("value"); It should_return_a_key_value_node = () => result.ShouldBeOfExactType(); @@ -66,7 +66,7 @@ class with_readonly_field ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Field").ShouldNotBeEmpty(); It should_retrieve_value = () => - ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Field").Single().ValueGetter().ShouldEqual("value"); + ((KeyValueNode) result).KeyValues.Single(m => m.Name == "Field").ValueGetter().ShouldEqual("value"); It should_return_a_key_value_node = () => result.ShouldBeOfExactType(); @@ -92,10 +92,10 @@ class with_property_and_field ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Property").ShouldNotBeEmpty(); It should_retrieve_field_value = () => - ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Field").Single().ValueGetter().ShouldEqual("value2"); + ((KeyValueNode) result).KeyValues.Single(m => m.Name == "Field").ValueGetter().ShouldEqual("value2"); It should_retrieve_property_value = () => - ((KeyValueNode) result).KeyValues.Where(m => m.Name == "Property").Single().ValueGetter().ShouldEqual("value1"); + ((KeyValueNode) result).KeyValues.Single(m => m.Name == "Property").ValueGetter().ShouldEqual("value1"); It should_return_a_key_value_node = () => result.ShouldBeOfExactType(); diff --git a/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs b/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs index 63432331..9ee421b1 100644 --- a/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs +++ b/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs @@ -88,7 +88,7 @@ class with_multiple_errors class with_using_object_multiple_times_in_expected_object_graph { - // Regression test for issue 17: ShouldBeLikeInternal() must mark as visisted instead of simply marking . + // Regression test for issue 17: ShouldBeLikeInternal() must mark as visited instead of simply marking . static Dummy a; static Dummy b; diff --git a/src/Machine.Specifications.Should/ComparableExtensions.cs b/src/Machine.Specifications.Should/ComparableExtensions.cs index 40325e43..04c0635e 100644 --- a/src/Machine.Specifications.Should/ComparableExtensions.cs +++ b/src/Machine.Specifications.Should/ComparableExtensions.cs @@ -1,6 +1,4 @@ using System; -using System.Linq; -using Machine.Specifications.Formatting; namespace Machine.Specifications { @@ -15,12 +13,12 @@ public static void ShouldBeGreaterThan(this IComparable arg1, IComparable arg2) if (arg1 == null) { - throw NewException("Should be greater than {0} but is [null]", arg2); + throw Exceptions.Specification("Should be greater than {0} but is [null]", arg2); } if (arg1.CompareTo(arg2.TryToChangeType(arg1.GetType())) <= 0) { - throw NewException("Should be greater than {0} but is {1}", arg2, arg1); + throw Exceptions.Specification("Should be greater than {0} but is {1}", arg2, arg1); } } @@ -33,12 +31,12 @@ public static void ShouldBeGreaterThanOrEqualTo(this IComparable arg1, IComparab if (arg1 == null) { - throw NewException("Should be greater than or equal to {0} but is [null]", arg2); + throw Exceptions.Specification("Should be greater than or equal to {0} but is [null]", arg2); } if (arg1.CompareTo(arg2.TryToChangeType(arg1.GetType())) < 0) { - throw NewException("Should be greater than or equal to {0} but is {1}", arg2, arg1); + throw Exceptions.Specification("Should be greater than or equal to {0} but is {1}", arg2, arg1); } } @@ -51,12 +49,12 @@ public static void ShouldBeLessThan(this IComparable arg1, IComparable arg2) if (arg1 == null) { - throw NewException("Should be less than {0} but is [null]", arg2); + throw Exceptions.Specification("Should be less than {0} but is [null]", arg2); } if (arg1.CompareTo(arg2.TryToChangeType(arg1.GetType())) >= 0) { - throw NewException("Should be less than {0} but is {1}", arg2, arg1); + throw Exceptions.Specification("Should be less than {0} but is {1}", arg2, arg1); } } @@ -69,16 +67,16 @@ public static void ShouldBeLessThanOrEqualTo(this IComparable arg1, IComparable if (arg1 == null) { - throw NewException("Should be less than or equal to {0} but is [null]", arg2); + throw Exceptions.Specification("Should be less than or equal to {0} but is [null]", arg2); } if (arg1.CompareTo(arg2.TryToChangeType(arg1.GetType())) > 0) { - throw NewException("Should be less than or equal to {0} but is {1}", arg2, arg1); + throw Exceptions.Specification("Should be less than or equal to {0} but is {1}", arg2, arg1); } } - private static object TryToChangeType(this object original, Type type) + private static object TryToChangeType(this IComparable original, Type type) { try { @@ -89,15 +87,5 @@ private static object TryToChangeType(this object original, Type type) return original; } } - - private static SpecificationException NewException(string message, params object[] parameters) - { - if (parameters.Any()) - { - return new SpecificationException(string.Format(message.EnsureSafeFormat(), parameters.Select(x => x.ToUsefulString()).Cast().ToArray())); - } - - return new SpecificationException(message); - } } } diff --git a/src/Machine.Specifications.Should/Comparers/AssertEqualityComparer.cs b/src/Machine.Specifications.Should/Comparers/AssertEqualityComparer.cs index dd680142..7c884475 100644 --- a/src/Machine.Specifications.Should/Comparers/AssertEqualityComparer.cs +++ b/src/Machine.Specifications.Should/Comparers/AssertEqualityComparer.cs @@ -4,7 +4,7 @@ namespace Machine.Specifications.Comparers { internal class AssertEqualityComparer : IEqualityComparer { - private static IEqualityStrategy[] Strategies = new IEqualityStrategy[] + private static readonly IEqualityStrategy[] Strategies = { new NullStrategy(), new EquatableStrategy(), @@ -14,7 +14,7 @@ internal class AssertEqualityComparer : IEqualityComparer new GenericTypeStrategy() }; - public static readonly AssertEqualityComparer Default = new AssertEqualityComparer(); + public static readonly AssertEqualityComparer Default = new(); public bool Equals(T? x, T? y) { diff --git a/src/Machine.Specifications.Should/Comparers/IEqualityStrategy.cs b/src/Machine.Specifications.Should/Comparers/IEqualityStrategy.cs index a94b8b2b..bb8c10bf 100644 --- a/src/Machine.Specifications.Should/Comparers/IEqualityStrategy.cs +++ b/src/Machine.Specifications.Should/Comparers/IEqualityStrategy.cs @@ -1,6 +1,6 @@ namespace Machine.Specifications.Comparers { - internal interface IEqualityStrategy + internal interface IEqualityStrategy { bool? Equals(T? x, T? y); } diff --git a/src/Machine.Specifications.Should/DateTimeExtensions.cs b/src/Machine.Specifications.Should/DateTimeExtensions.cs index 30a7f26d..a2728b79 100644 --- a/src/Machine.Specifications.Should/DateTimeExtensions.cs +++ b/src/Machine.Specifications.Should/DateTimeExtensions.cs @@ -9,7 +9,7 @@ public static void ShouldBeCloseTo(this TimeSpan actual, TimeSpan expected, Time { if (Math.Abs(actual.Ticks - expected.Ticks) > tolerance.Ticks) { - throw new SpecificationException($"Should be within {tolerance.ToUsefulString()} of {expected.ToUsefulString()} but is {actual.ToUsefulString()}"); + throw new SpecificationException($"Should be within {tolerance.Format()} of {expected.Format()} but is {actual.Format()}"); } } @@ -19,7 +19,7 @@ public static void ShouldBeCloseTo(this DateTime actual, DateTime expected, Time if (Math.Abs(difference.Ticks) > tolerance.Ticks) { - throw new SpecificationException($"Should be within {tolerance.ToUsefulString()} of {expected.ToUsefulString()} but is {actual.ToUsefulString()}"); + throw new SpecificationException($"Should be within {tolerance.Format()} of {expected.Format()} but is {actual.Format()}"); } } } diff --git a/src/Machine.Specifications.Should/EnumerableExtensions.cs b/src/Machine.Specifications.Should/EnumerableExtensions.cs index 7aa445c7..d59bbbae 100644 --- a/src/Machine.Specifications.Should/EnumerableExtensions.cs +++ b/src/Machine.Specifications.Should/EnumerableExtensions.cs @@ -135,7 +135,7 @@ public static void ShouldBeEmpty(this IEnumerable collection) if (items.Any()) { - throw NewException("Should be empty but contains:\n" + items.EachToUsefulString()); + throw Exceptions.Specification("Should be empty but contains:\n" + items.EachToUsefulString()); } } @@ -143,7 +143,7 @@ public static void ShouldNotBeEmpty(this IEnumerable collection) { if (!collection.Cast().Any()) { - throw NewException("Should not be empty but is"); + throw Exceptions.Specification("Should not be empty but is"); } } @@ -191,15 +191,5 @@ public static void ShouldContainOnly(this IEnumerable list, IEnumerable throw new SpecificationException(message); } } - - private static SpecificationException NewException(string message, params object[] parameters) - { - if (parameters.Any()) - { - return new SpecificationException(string.Format(message.EnsureSafeFormat(), parameters.Select(x => x.ToUsefulString()).Cast().ToArray())); - } - - return new SpecificationException(message); - } } } diff --git a/src/Machine.Specifications.Should/EqualityExtensions.cs b/src/Machine.Specifications.Should/EqualityExtensions.cs index b83082c9..0db71f34 100644 --- a/src/Machine.Specifications.Should/EqualityExtensions.cs +++ b/src/Machine.Specifications.Should/EqualityExtensions.cs @@ -2,7 +2,6 @@ using System.Linq.Expressions; using Machine.Specifications.Comparers; using Machine.Specifications.Formatting; -using Machine.Specifications.Utility.Internal; namespace Machine.Specifications { @@ -12,7 +11,7 @@ public static void ShouldEqual(this T actual, T expected) { if (!AssertEqualityComparer.Default.Equals(actual, expected)) { - throw new SpecificationException(PrettyPrintingExtensions.FormatErrorMessage(actual, expected)); + throw new SpecificationException(actual.FormatErrorMessage(expected)); } } @@ -20,7 +19,7 @@ public static void ShouldNotEqual(this T actual, T expected) { if (AssertEqualityComparer.Default.Equals(actual, expected)) { - throw new SpecificationException($"Should not equal {expected.ToUsefulString()} but does: {actual.ToUsefulString()}"); + throw new SpecificationException($"Should not equal {expected.Format()} but does: {actual.Format()}"); } } diff --git a/src/Machine.Specifications.Should/EquivalenceExtensions.cs b/src/Machine.Specifications.Should/EquivalenceExtensions.cs index dd87165f..0d1f8192 100644 --- a/src/Machine.Specifications.Should/EquivalenceExtensions.cs +++ b/src/Machine.Specifications.Should/EquivalenceExtensions.cs @@ -4,7 +4,6 @@ using System.Runtime.CompilerServices; using Machine.Specifications.Formatting; using Machine.Specifications.Reflection; -using Machine.Specifications.Utility.Internal; namespace Machine.Specifications { @@ -12,39 +11,28 @@ public static class EquivalenceExtensions { public static void ShouldBeLike(this object obj, object expected) { - var exceptions = ShouldBeLikeInternal(obj, expected, string.Empty, new HashSet()).ToArray(); + var exceptions = ShouldBeLike(obj, expected, string.Empty, new HashSet()).ToArray(); if (exceptions.Any()) { - throw NewException(exceptions.Select(e => e.Message).Aggregate(string.Empty, (r, m) => r + m + Environment.NewLine + Environment.NewLine).TrimEnd()); + throw Exceptions.Specification(exceptions.Select(e => e.Message).Aggregate(string.Empty, (r, m) => r + m + Environment.NewLine + Environment.NewLine).TrimEnd()); } } - private static SpecificationException NewException(string message, params object[] parameters) - { - if (parameters.Any()) - { - return new SpecificationException(string.Format(message.EnsureSafeFormat(), parameters.Select(x => x.ToUsefulString()).Cast().ToArray())); - } - - return new SpecificationException(message); - } - - private static IEnumerable ShouldBeLikeInternal(object obj, object expected, string nodeName, HashSet visited) + private static IEnumerable ShouldBeLike(object? obj, object? expected, string nodeName, ISet visited) { // Stop at already checked -pairs to prevent infinite loops (cycles in object graphs). Additionally // this also avoids re-equality-evaluation for already compared pairs. - var objExpectedTuple = new ReferentialEqualityTuple(obj, expected); + var tuple = new ReferentialEqualityTuple(obj, expected); - if (visited.Contains(objExpectedTuple)) + if (visited.Contains(tuple)) { return Enumerable.Empty(); } - visited.Add(objExpectedTuple); + visited.Add(tuple); var expectedNode = default(INode); - var nodeType = typeof(LiteralNode); if (obj != null && expected != null) @@ -61,7 +49,7 @@ private static IEnumerable ShouldBeLikeInternal(object o } catch (SpecificationException ex) { - return new[] { NewException($"{{0}}:{Environment.NewLine}{ex.Message}", nodeName) }; + return new[] { Exceptions.Specification($"{{0}}:{Environment.NewLine}{ex.Message}", nodeName) }; } return Enumerable.Empty(); @@ -71,9 +59,9 @@ private static IEnumerable ShouldBeLikeInternal(object o { if (obj == null) { - var errorMessage = PrettyPrintingExtensions.FormatErrorMessage(null, expected); + var errorMessage = ObjectExtensions.FormatErrorMessage(null, expected); - return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; + return new[] { Exceptions.Specification($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; } var actualNode = ObjectGraph.Get(obj); @@ -82,10 +70,10 @@ private static IEnumerable ShouldBeLikeInternal(object o { var errorMessage = $" Expected: Array or Sequence{Environment.NewLine} But was: {obj.GetType()}"; - return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; + return new[] { Exceptions.Specification($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; } - var expectedValues = ((SequenceNode) expectedNode)?.ValueGetters.ToArray(); + var expectedValues = ((SequenceNode) expectedNode!).ValueGetters.ToArray(); var actualValues = ((SequenceNode) actualNode).ValueGetters.ToArray(); var expectedCount = expectedValues?.Length ?? 0; @@ -95,44 +83,44 @@ private static IEnumerable ShouldBeLikeInternal(object o { var errorMessage = string.Format(" Expected: Sequence length of {1}{0} But was: {2}", Environment.NewLine, expectedCount, actualCount); - return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; + return new[] { Exceptions.Specification($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; } return Enumerable.Range(0, expectedCount) - .SelectMany(i => ShouldBeLikeInternal(actualValues.ElementAt(i)(), expectedValues?.ElementAt(i)(), $"{nodeName}[{i}]", visited)); + .SelectMany(i => ShouldBeLike(actualValues.ElementAt(i)(), expectedValues?.ElementAt(i)(), $"{nodeName}[{i}]", visited)); } if (nodeType == typeof(KeyValueNode)) { - var actualNode = ObjectGraph.Get(obj); + var actualNode = ObjectGraph.Get(obj!); if (actualNode.GetType() != typeof(KeyValueNode)) { var errorMessage = $" Expected: Class{Environment.NewLine} But was: {obj?.GetType()}"; - return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; + return new[] { Exceptions.Specification($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; } - var expectedKeyValues = ((KeyValueNode) expectedNode)?.KeyValues; + var expectedKeyValues = ((KeyValueNode) expectedNode!).KeyValues; var actualKeyValues = ((KeyValueNode) actualNode).KeyValues; - return expectedKeyValues?.SelectMany(kv => - { - var fullNodeName = string.IsNullOrEmpty(nodeName) - ? kv.Name - : $"{nodeName}.{kv.Name}"; - var actualKeyValue = actualKeyValues.SingleOrDefault(k => k.Name == kv.Name); - - if (actualKeyValue == null) + return expectedKeyValues + .SelectMany(kv => { - var errorMessage = string.Format(" Expected: {1}{0} But was: Not Defined", - Environment.NewLine, kv.ValueGetter().ToUsefulString()); + var fullNodeName = string.IsNullOrEmpty(nodeName) + ? kv.Name + : $"{nodeName}.{kv.Name}"; + var actualKeyValue = actualKeyValues.SingleOrDefault(k => k.Name == kv.Name); + + if (actualKeyValue == null) + { + var errorMessage = string.Format(" Expected: {1}{0} But was: Not Defined", Environment.NewLine, kv.ValueGetter().Format()); - return new[] { NewException($"{{0}}:{Environment.NewLine}{errorMessage}", fullNodeName) }; - } + return new[] {Exceptions.Specification($"{{0}}:{Environment.NewLine}{errorMessage}", fullNodeName)}; + } - return ShouldBeLikeInternal(actualKeyValue.ValueGetter(), kv.ValueGetter(), fullNodeName, visited); - }); + return ShouldBeLike(actualKeyValue.ValueGetter(), kv.ValueGetter(), fullNodeName, visited); + }); } throw new InvalidOperationException("Unknown node type"); @@ -140,11 +128,11 @@ private static IEnumerable ShouldBeLikeInternal(object o private class ReferentialEqualityTuple { - private readonly object value; + private readonly object? value; - private readonly object expected; + private readonly object? expected; - public ReferentialEqualityTuple(object value, object expected) + public ReferentialEqualityTuple(object? value, object? expected) { this.value = value; this.expected = expected; @@ -157,12 +145,13 @@ public override int GetHashCode() public override bool Equals(object other) { - if (!(other is ReferentialEqualityTuple otherSimpleTuple)) + if (other is not ReferentialEqualityTuple otherTuple) { return false; } - return ReferenceEquals(value, otherSimpleTuple.value) && ReferenceEquals(expected, otherSimpleTuple.expected); + return ReferenceEquals(value, otherTuple.value) && + ReferenceEquals(expected, otherTuple.expected); } } } diff --git a/src/Machine.Specifications.Should/ExceptionExtensions.cs b/src/Machine.Specifications.Should/ExceptionExtensions.cs index 1f6eb217..faa711f5 100644 --- a/src/Machine.Specifications.Should/ExceptionExtensions.cs +++ b/src/Machine.Specifications.Should/ExceptionExtensions.cs @@ -8,29 +8,5 @@ public static void ShouldContainErrorMessage(this Exception exception, string ex { exception.Message.ShouldContain(expected); } - - public static Exception ShouldBeThrownBy(this Type exceptionType, Action method) - { - var exception = CatchException(method); - - exception.ShouldNotBeNull(); - exception.ShouldBeAssignableTo(exceptionType); - - return exception; - } - - private static Exception CatchException(Action throwingAction) - { - try - { - throwingAction(); - } - catch (Exception ex) - { - return ex; - } - - return null; - } } } diff --git a/src/Machine.Specifications.Should/Exceptions.cs b/src/Machine.Specifications.Should/Exceptions.cs new file mode 100644 index 00000000..aabe448b --- /dev/null +++ b/src/Machine.Specifications.Should/Exceptions.cs @@ -0,0 +1,28 @@ +using System.Linq; +using System.Text.RegularExpressions; +using Machine.Specifications.Formatting; + +namespace Machine.Specifications +{ + internal static class Exceptions + { + private const string EscapedBraces = "{{{0}}}"; + + private static readonly Regex FormatBraces = new(@"{([^\d].*?)}", RegexOptions.Compiled | RegexOptions.Singleline); + + public static SpecificationException Specification(string message, params object[] parameters) + { + if (parameters.Any()) + { + return new SpecificationException(string.Format(EnsureSafeFormat(message), parameters.Select(x => x.Format()).Cast().ToArray())); + } + + return new SpecificationException(message); + } + + private static string EnsureSafeFormat(string message) + { + return FormatBraces.Replace(message, match => string.Format(EscapedBraces, match.Groups[0])); + } + } +} diff --git a/src/Machine.Specifications.Should/FloatingPointExtensions.cs b/src/Machine.Specifications.Should/FloatingPointExtensions.cs index de681b64..cbed3a33 100644 --- a/src/Machine.Specifications.Should/FloatingPointExtensions.cs +++ b/src/Machine.Specifications.Should/FloatingPointExtensions.cs @@ -14,7 +14,7 @@ public static void ShouldBeCloseTo(this float actual, float expected, float tole { if (Math.Abs(actual - expected) > tolerance) { - throw new SpecificationException($"Should be within {tolerance.ToUsefulString()} of {expected.ToUsefulString()} but is {actual.ToUsefulString()}"); + throw new SpecificationException($"Should be within {tolerance.Format()} of {expected.Format()} but is {actual.Format()}"); } } @@ -27,7 +27,7 @@ public static void ShouldBeCloseTo(this double actual, double expected, double t { if (Math.Abs(actual - expected) > tolerance) { - throw new SpecificationException($"Should be within {tolerance.ToUsefulString()} of {expected.ToUsefulString()} but is {actual.ToUsefulString()}"); + throw new SpecificationException($"Should be within {tolerance.Format()} of {expected.Format()} but is {actual.Format()}"); } } @@ -40,7 +40,7 @@ public static void ShouldBeCloseTo(this decimal actual, decimal expected, decima { if (Math.Abs(actual - expected) > tolerance) { - throw new SpecificationException($"Should be within {tolerance.ToUsefulString()} of {expected.ToUsefulString()} but is {actual.ToUsefulString()}"); + throw new SpecificationException($"Should be within {tolerance.Format()} of {expected.Format()} but is {actual.Format()}"); } } } diff --git a/src/Machine.Specifications.Should/Formatting/EnumerableExtensions.cs b/src/Machine.Specifications.Should/Formatting/EnumerableExtensions.cs index 9d5503f8..745038ed 100644 --- a/src/Machine.Specifications.Should/Formatting/EnumerableExtensions.cs +++ b/src/Machine.Specifications.Should/Formatting/EnumerableExtensions.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text; @@ -10,7 +9,7 @@ internal static class EnumerableExtensions public static string EachToUsefulString(this IEnumerable enumerable) { var array = enumerable.ToArray(); - var arrayValues = array.Select(x => x.ToUsefulString().Indent()) + var arrayValues = array.Select(x => x.Format().Indent()) .Take(10) .ToArray(); @@ -26,7 +25,7 @@ public static string EachToUsefulString(this IEnumerable enumerable) } else { - result.AppendLine(",\r\n" + array.Last().ToUsefulString().Indent()); + result.AppendLine(",\r\n" + array.Last().Format().Indent()); } } else diff --git a/src/Machine.Specifications.Should/Formatting/ObjectExtensions.cs b/src/Machine.Specifications.Should/Formatting/ObjectExtensions.cs index 877d23af..37181e31 100644 --- a/src/Machine.Specifications.Should/Formatting/ObjectExtensions.cs +++ b/src/Machine.Specifications.Should/Formatting/ObjectExtensions.cs @@ -1,24 +1,26 @@ -using System.Collections; +using System; +using System.Collections; using System.Linq; -using System.Reflection; namespace Machine.Specifications.Formatting { internal static class ObjectExtensions { - internal static string ToUsefulString(this object value) + public static string Format(this object? value) { if (value == null) { return "[null]"; } - if (value is string stringValue) + if (value is string valueAsString) { - return "\"" + stringValue.Replace("\n", "\\n") + "\""; + return $@"""{valueAsString.Replace("\n", "\\n")}"""; } - if (value.GetType().GetTypeInfo().IsValueType) + var type = value.GetType(); + + if (type.IsValueType) { return "[" + value + "]"; } @@ -27,32 +29,150 @@ internal static string ToUsefulString(this object value) { var enumerable = items.Cast(); - return items.GetType() + ":\r\n" + enumerable.EachToUsefulString(); + return type + ":\r\n" + enumerable.EachToUsefulString(); } - var str = value.ToString(); + var stringValue = value.ToString(); - if (str == null || str.Trim() == string.Empty) + if (stringValue == null || stringValue.Trim() == string.Empty) { - return $"{value.GetType()}:[]"; + return $"{type}:[]"; } - str = str.Trim(); + stringValue = stringValue.Trim(); - if (str.Contains("\n")) + if (stringValue.Contains("\n")) { return string.Format(@"{1}: [ {0} -]", str.Indent(), value.GetType()); +]", stringValue.Indent(), type); + } + + var typeString = type.ToString(); + + if (typeString == stringValue) + { + return typeString; + } + + return $"{type}:[{stringValue}]"; + } + + public static string FormatErrorMessage(this object? actualObject, object? expectedObject) + { + if (actualObject is string actual && expectedObject is string expected) + { + var message = GetExpectedStringLengthMessage(expected.Length, actual.Length); + var index = IndexOf(actual, expected); + + GetStringsAroundFirstDifference(expected, actual, index, out var actualReported, out var expectedReported); + + var count = IndexOf(actualReported, expectedReported); + + return string.Format( + " {1} Strings differ at index {2}.{0}" + + " Expected: \"{3}\"{0}" + + " But was: \"{4}\"{0}" + + " -----------{5}^", + Environment.NewLine, + message, + index, + expectedReported, + actualReported, + new string('-', count)); } - if (value.GetType().ToString() == str) + var actualValue = actualObject.Format(); + var expectedValue = expectedObject.Format(); + + return string.Format(" Expected: {1}{0} But was: {2}", Environment.NewLine, expectedValue, actualValue); + } + + private static void GetStringsAroundFirstDifference(string expected, string actual, int index, out string actualReported, out string expectedReported) + { + var left = index; + var actualRight = index; + var expectedRight = index; + var keepAugmenting = true; + + while (keepAugmenting && IsInCopyFrameLength(left, actualRight, actual.Length) && IsInCopyFrameLength(left, expectedRight, expected.Length)) { - return value.GetType().ToString(); + keepAugmenting = false; + + if (left > 0) + { + left--; + keepAugmenting = true; + } + + if (IsInCopyFrameLength(left, actualRight, actual.Length) && IsInCopyFrameLength(left, expectedRight, expected.Length)) + { + if (actual.Length > actualRight) + { + actualRight++; + keepAugmenting = true; + } + + if (expected.Length > expectedRight) + { + expectedRight++; + keepAugmenting = true; + } + } } - return $"{value.GetType()}:[{str}]"; + actualReported = actual.Substring(left, actualRight - left); + expectedReported = expected.Substring(left, expectedRight - left); + + if (left != 0) + { + actualReported = "..." + actualReported; + expectedReported = "..." + expectedReported; + } + + if (actualRight != actual.Length || expectedRight != expected.Length) + { + actualReported += "..."; + expectedReported += "..."; + } + } + + private static bool IsInCopyFrameLength(int start, int end, int max) + { + var length = end - start; + + if (start > 0) + { + length += 3; + } + + if (end < max) + { + length += 3; + } + + return length < 64; + } + + private static int IndexOf(string actual, string expected) + { + for (var i = 0; i < actual.Length; i++) + { + if (expected.Length <= i || expected[i] != actual[i]) + { + return i; + } + } + + return actual.Length; + } + + private static string GetExpectedStringLengthMessage(int actual, int expected) + { + return actual == expected + ? $"String lengths are both {actual}." + : $"Expected string length {actual} but was {expected}."; } } } diff --git a/src/Machine.Specifications.Should/Formatting/ObjectFormatter.cs b/src/Machine.Specifications.Should/Formatting/ObjectFormatter.cs deleted file mode 100644 index 8f25bd2f..00000000 --- a/src/Machine.Specifications.Should/Formatting/ObjectFormatter.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections; -using System.Linq; - -namespace Machine.Specifications.Formatting -{ - internal static class ObjectFormatter - { - public static string Format(object value) - { - if (value == null) - { - return "[null]"; - } - - if (value is string valueAsString) - { - return $@"""{valueAsString.Replace("\n", "\\n")}"""; - } - - var type = value.GetType(); - - if (type.IsValueType) - { - return "[" + value + "]"; - } - - if (value is IEnumerable items) - { - var enumerable = items.Cast(); - - return type + ":\r\n" + enumerable.EachToUsefulString(); - } - - var stringValue = value.ToString(); - - if (stringValue == null || stringValue.Trim() == string.Empty) - { - return $"{type}:[]"; - } - - stringValue = stringValue.Trim(); - - if (stringValue.Contains("\n")) - { - return string.Format(@"{1}: -[ -{0} -]", stringValue.Indent(), type); - } - - var typeString = type.ToString(); - - if (typeString == stringValue) - { - return typeString; - } - - return $"{type}:[{stringValue}]"; - } - } -} diff --git a/src/Machine.Specifications.Should/Formatting/StringExtensions.cs b/src/Machine.Specifications.Should/Formatting/StringExtensions.cs index 8d4558d8..586f82b8 100644 --- a/src/Machine.Specifications.Should/Formatting/StringExtensions.cs +++ b/src/Machine.Specifications.Should/Formatting/StringExtensions.cs @@ -1,16 +1,11 @@ using System; using System.Linq; using System.Text; -using System.Text.RegularExpressions; namespace Machine.Specifications.Formatting { internal static class StringExtensions { - private const string EscapedBraces = "{{{0}}}"; - - private static readonly Regex FormatBraces = new Regex(@"{([^\d].*?)}", RegexOptions.Compiled | RegexOptions.Singleline); - private static readonly string[] Delimiters = { "\r\n", @@ -35,10 +30,5 @@ public static string Indent(this string value) return result.ToString(); } - - public static string EnsureSafeFormat(this string message) - { - return FormatBraces.Replace(message, match => string.Format(EscapedBraces, match.Groups[0])); - } } } diff --git a/src/Machine.Specifications.Should/NullExtensions.cs b/src/Machine.Specifications.Should/NullExtensions.cs index 0ebab00b..1c89d680 100644 --- a/src/Machine.Specifications.Should/NullExtensions.cs +++ b/src/Machine.Specifications.Should/NullExtensions.cs @@ -6,18 +6,18 @@ namespace Machine.Specifications public static class NullExtensions { [AssertionMethod] - public static void ShouldBeNull([AssertionCondition(AssertionConditionType.IS_NULL)] this object anObject) + public static void ShouldBeNull([AssertionCondition(AssertionConditionType.IS_NULL)] this object? value) { - if (anObject != null) + if (value != null) { - throw new SpecificationException($"Should be [null] but is {anObject.ToUsefulString()}"); + throw new SpecificationException($"Should be [null] but is {value.Format()}"); } } [AssertionMethod] - public static void ShouldNotBeNull([AssertionCondition(AssertionConditionType.IS_NOT_NULL)] this object anObject) + public static void ShouldNotBeNull([AssertionCondition(AssertionConditionType.IS_NOT_NULL)] this object? value) { - if (anObject == null) + if (value == null) { throw new SpecificationException("Should be [not null] but is [null]"); } diff --git a/src/Machine.Specifications.Should/Reflection/KeyValueNode.cs b/src/Machine.Specifications.Should/Reflection/KeyValueNode.cs index 31129426..082cb9ed 100644 --- a/src/Machine.Specifications.Should/Reflection/KeyValueNode.cs +++ b/src/Machine.Specifications.Should/Reflection/KeyValueNode.cs @@ -1,9 +1,12 @@ -using System.Collections.Generic; - -namespace Machine.Specifications.Reflection +namespace Machine.Specifications.Reflection { internal class KeyValueNode : INode { - public IEnumerable KeyValues { get; set; } + public KeyValueNode(Member[] keyValues) + { + KeyValues = keyValues; + } + + public Member[] KeyValues { get; } } } diff --git a/src/Machine.Specifications.Should/Reflection/LiteralNode.cs b/src/Machine.Specifications.Should/Reflection/LiteralNode.cs index ff9d405f..d1fabcfd 100644 --- a/src/Machine.Specifications.Should/Reflection/LiteralNode.cs +++ b/src/Machine.Specifications.Should/Reflection/LiteralNode.cs @@ -2,6 +2,11 @@ { internal class LiteralNode : INode { - public object Value { get; set; } + public LiteralNode(object value) + { + Value = value; + } + + public object Value { get; } } } diff --git a/src/Machine.Specifications.Should/Reflection/Member.cs b/src/Machine.Specifications.Should/Reflection/Member.cs index d0ed44f8..829d04d5 100644 --- a/src/Machine.Specifications.Should/Reflection/Member.cs +++ b/src/Machine.Specifications.Should/Reflection/Member.cs @@ -4,8 +4,14 @@ namespace Machine.Specifications.Reflection { internal class Member { - public string Name { get; set; } + public Member(string name, Func valueGetter) + { + Name = name; + ValueGetter = valueGetter; + } - public Func ValueGetter { get; set; } + public string Name { get; } + + public Func ValueGetter { get; } } } diff --git a/src/Machine.Specifications.Should/Reflection/ObjectGraph.cs b/src/Machine.Specifications.Should/Reflection/ObjectGraph.cs index e81ce95d..2cbf8530 100644 --- a/src/Machine.Specifications.Should/Reflection/ObjectGraph.cs +++ b/src/Machine.Specifications.Should/Reflection/ObjectGraph.cs @@ -20,10 +20,7 @@ public static INode Get(object value) return GetKeyValueNode(value); } - return new LiteralNode - { - Value = value - }; + return new LiteralNode(value); } private static INode GetSequenceNode(IEnumerable value) @@ -32,10 +29,7 @@ private static INode GetSequenceNode(IEnumerable value) .Select>(x => () => x) .ToArray(); - return new SequenceNode - { - ValueGetters = getters - }; + return new SequenceNode(getters); } private static INode GetKeyValueNode(object value) @@ -45,30 +39,13 @@ private static INode GetKeyValueNode(object value) var properties = type .GetProperties() .Where(x => x.CanRead && !x.GetGetMethod().IsStatic) - .Select(x => - { - return new Member - { - Name = x.Name, - ValueGetter = () => x.GetValue(value, null) - }; - }); + .Select(x => new Member(x.Name, () => x.GetValue(value, null))); var fields = type .GetFields() - .Select(x => - { - return new Member - { - Name = x.Name, - ValueGetter = () => x.GetValue(value) - }; - }); + .Select(x => new Member(x.Name, () => x.GetValue(value))); - return new KeyValueNode - { - KeyValues = properties.Concat(fields).OrderBy(m => m.Name) - }; + return new KeyValueNode(properties.Concat(fields).OrderBy(m => m.Name).ToArray()); } } } diff --git a/src/Machine.Specifications.Should/Reflection/SequenceNode.cs b/src/Machine.Specifications.Should/Reflection/SequenceNode.cs index 1790e796..3b0eabe8 100644 --- a/src/Machine.Specifications.Should/Reflection/SequenceNode.cs +++ b/src/Machine.Specifications.Should/Reflection/SequenceNode.cs @@ -1,10 +1,14 @@ using System; -using System.Collections.Generic; namespace Machine.Specifications.Reflection { internal class SequenceNode : INode { - public IEnumerable> ValueGetters { get; set; } + public SequenceNode(Func[] valueGetters) + { + ValueGetters = valueGetters; + } + + public Func[] ValueGetters { get; } } } diff --git a/src/Machine.Specifications.Should/StringExtensions.cs b/src/Machine.Specifications.Should/StringExtensions.cs index d1ec4d69..492e6991 100644 --- a/src/Machine.Specifications.Should/StringExtensions.cs +++ b/src/Machine.Specifications.Should/StringExtensions.cs @@ -1,14 +1,12 @@ using System; using System.Globalization; -using System.Linq; using System.Text.RegularExpressions; -using Machine.Specifications.Formatting; namespace Machine.Specifications { public static class StringExtensions { - public static void ShouldBeEmpty(this string value) + public static void ShouldBeEmpty(this string? value) { if (value == null) { @@ -17,19 +15,19 @@ public static void ShouldBeEmpty(this string value) if (!string.IsNullOrEmpty(value)) { - throw NewException("Should be empty but is {0}", value); + throw Exceptions.Specification("Should be empty but is {0}", value); } } - public static void ShouldNotBeEmpty(this string value) + public static void ShouldNotBeEmpty(this string? value) { if (string.IsNullOrEmpty(value)) { - throw NewException("Should not be empty but is"); + throw Exceptions.Specification("Should not be empty but is"); } } - public static void ShouldMatch(this string actual, string pattern) + public static void ShouldMatch(this string? actual, string pattern) { if (pattern == null) { @@ -38,13 +36,13 @@ public static void ShouldMatch(this string actual, string pattern) if (actual == null) { - throw NewException("Should match regex {0} but is [null]", pattern); + throw Exceptions.Specification("Should match regex {0} but is [null]", pattern); } ShouldMatch(actual, new Regex(pattern)); } - public static void ShouldMatch(this string actual, Regex pattern) + public static void ShouldMatch(this string? actual, Regex pattern) { if (pattern == null) { @@ -53,16 +51,16 @@ public static void ShouldMatch(this string actual, Regex pattern) if (actual == null) { - throw NewException("Should match regex {0} but is [null]", pattern); + throw Exceptions.Specification("Should match regex {0} but is [null]", pattern); } if (!pattern.IsMatch(actual)) { - throw NewException("Should match {0} but is {1}", pattern, actual); + throw Exceptions.Specification("Should match {0} but is {1}", pattern, actual); } } - public static void ShouldContain(this string actual, string expected) + public static void ShouldContain(this string? actual, string expected) { if (expected == null) { @@ -71,16 +69,16 @@ public static void ShouldContain(this string actual, string expected) if (actual == null) { - throw NewException("Should contain {0} but is [null]", expected); + throw Exceptions.Specification("Should contain {0} but is [null]", expected); } if (!actual.Contains(expected)) { - throw NewException("Should contain {0} but is {1}", expected, actual); + throw Exceptions.Specification("Should contain {0} but is {1}", expected, actual); } } - public static void ShouldNotContain(this string actual, string notExpected) + public static void ShouldNotContain(this string? actual, string notExpected) { if (notExpected == null) { @@ -94,11 +92,11 @@ public static void ShouldNotContain(this string actual, string notExpected) if (actual.Contains(notExpected)) { - throw NewException("Should not contain {0} but is {1}", notExpected, actual); + throw Exceptions.Specification("Should not contain {0} but is {1}", notExpected, actual); } } - public static string ShouldBeEqualIgnoringCase(this string actual, string expected) + public static string ShouldBeEqualIgnoringCase(this string? actual, string expected) { if (expected == null) { @@ -107,18 +105,18 @@ public static string ShouldBeEqualIgnoringCase(this string actual, string expect if (actual == null) { - throw NewException("Should be equal ignoring case to {0} but is [null]", expected); + throw Exceptions.Specification("Should be equal ignoring case to {0} but is [null]", expected); } if (CultureInfo.InvariantCulture.CompareInfo.Compare(actual, expected, CompareOptions.IgnoreCase) != 0) { - throw NewException("Should be equal ignoring case to {0} but is {1}", expected, actual); + throw Exceptions.Specification("Should be equal ignoring case to {0} but is {1}", expected, actual); } return actual; } - public static void ShouldStartWith(this string actual, string expected) + public static void ShouldStartWith(this string? actual, string expected) { if (expected == null) { @@ -127,16 +125,16 @@ public static void ShouldStartWith(this string actual, string expected) if (actual == null) { - throw NewException("Should start with {0} but is [null]", expected); + throw Exceptions.Specification("Should start with {0} but is [null]", expected); } if (!actual.StartsWith(expected)) { - throw NewException("Should start with {0} but is {1}", expected, actual); + throw Exceptions.Specification("Should start with {0} but is {1}", expected, actual); } } - public static void ShouldEndWith(this string actual, string expected) + public static void ShouldEndWith(this string? actual, string expected) { if (expected == null) { @@ -145,35 +143,25 @@ public static void ShouldEndWith(this string actual, string expected) if (actual == null) { - throw NewException("Should end with {0} but is [null]", expected); + throw Exceptions.Specification("Should end with {0} but is [null]", expected); } if (!actual.EndsWith(expected)) { - throw NewException("Should end with {0} but is {1}", expected, actual); + throw Exceptions.Specification("Should end with {0} but is {1}", expected, actual); } } - public static void ShouldBeSurroundedWith(this string actual, string expectedStartDelimiter, string expectedEndDelimiter) + public static void ShouldBeSurroundedWith(this string? actual, string expectedStartDelimiter, string expectedEndDelimiter) { actual.ShouldStartWith(expectedStartDelimiter); actual.ShouldEndWith(expectedEndDelimiter); } - public static void ShouldBeSurroundedWith(this string actual, string expectedDelimiter) + public static void ShouldBeSurroundedWith(this string? actual, string expectedDelimiter) { actual.ShouldStartWith(expectedDelimiter); actual.ShouldEndWith(expectedDelimiter); } - - private static SpecificationException NewException(string message, params object[] parameters) - { - if (parameters.Any()) - { - return new SpecificationException(string.Format(message.EnsureSafeFormat(), parameters.Select(x => x.ToUsefulString()).Cast().ToArray())); - } - - return new SpecificationException(message); - } } } diff --git a/src/Machine.Specifications.Should/TypeExtensions.cs b/src/Machine.Specifications.Should/TypeExtensions.cs index 6b59b609..e3bc92c7 100644 --- a/src/Machine.Specifications.Should/TypeExtensions.cs +++ b/src/Machine.Specifications.Should/TypeExtensions.cs @@ -4,7 +4,12 @@ namespace Machine.Specifications { public static class TypeExtensions { - public static void ShouldBeOfExactType(this object actual, Type expected) + public static void ShouldBeOfExactType(this object? actual) + { + actual.ShouldBeOfExactType(typeof(T)); + } + + public static void ShouldBeOfExactType(this object? actual, Type expected) { if (actual == null) { @@ -17,7 +22,12 @@ public static void ShouldBeOfExactType(this object actual, Type expected) } } - public static void ShouldNotBeOfExactType(this object actual, Type expected) + public static void ShouldNotBeOfExactType(this object? actual) + { + actual?.ShouldNotBeOfExactType(typeof(T)); + } + + public static void ShouldNotBeOfExactType(this object? actual, Type expected) { if (actual == null) { @@ -30,17 +40,12 @@ public static void ShouldNotBeOfExactType(this object actual, Type expected) } } - public static void ShouldBeOfExactType(this object actual) - { - actual.ShouldBeOfExactType(typeof(T)); - } - - public static void ShouldNotBeOfExactType(this object actual) + public static void ShouldBeAssignableTo(this object? actual) { - actual.ShouldNotBeOfExactType(typeof(T)); + actual?.ShouldBeAssignableTo(typeof(T)); } - public static void ShouldBeAssignableTo(this object actual, Type expected) + public static void ShouldBeAssignableTo(this object? actual, Type expected) { if (actual == null) { @@ -53,7 +58,12 @@ public static void ShouldBeAssignableTo(this object actual, Type expected) } } - public static void ShouldNotBeAssignableTo(this object actual, Type expected) + public static void ShouldNotBeAssignableTo(this object? actual) + { + actual?.ShouldNotBeAssignableTo(typeof(T)); + } + + public static void ShouldNotBeAssignableTo(this object? actual, Type expected) { if (actual == null) { @@ -65,15 +75,5 @@ public static void ShouldNotBeAssignableTo(this object actual, Type expected) throw new SpecificationException($"Should not be assignable to type {expected} but is. Actual type is {actual.GetType()}"); } } - - public static void ShouldBeAssignableTo(this object actual) - { - actual.ShouldBeAssignableTo(typeof(T)); - } - - public static void ShouldNotBeAssignableTo(this object actual) - { - actual.ShouldNotBeAssignableTo(typeof(T)); - } } } diff --git a/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs b/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs deleted file mode 100644 index b36ff821..00000000 --- a/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using Machine.Specifications.Formatting; - -namespace Machine.Specifications.Utility.Internal -{ - internal static class PrettyPrintingExtensions - { - public static string FormatErrorMessage(object actualObject, object expectedObject) - { - if (actualObject is string && expectedObject is string) - { - var actual = actualObject as string; - var expected = expectedObject as string; - - var expectedStringLengthMessage = GetExpectedStringLengthMessage(expected.Length, actual.Length); - var firstIndexOfFirstDifference = GetFirstIndexOfFirstDifference(actual, expected); - - GetStringsAroundFirstDifference(expected, actual, firstIndexOfFirstDifference, out var actualReported, out var expectedReported); - - var count = GetFirstIndexOfFirstDifference(actualReported, expectedReported); - - return string.Format( - " {1} Strings differ at index {2}.{0}" + - " Expected: \"{3}\"{0}" + - " But was: \"{4}\"{0}" + - " -----------{5}^", - Environment.NewLine, - expectedStringLengthMessage, - firstIndexOfFirstDifference, - expectedReported, - actualReported, - new string('-', count)); - } - - var actualValue = actualObject.ToUsefulString(); - var expectedValue = expectedObject.ToUsefulString(); - - return string.Format(" Expected: {1}{0} But was: {2}", Environment.NewLine, expectedValue, actualValue); - } - - private static void GetStringsAroundFirstDifference(string expected, string actual, int firstIndexOfFirstDifference, out string actualReported, out string expectedReported) - { - var left = firstIndexOfFirstDifference; - var actualRight = firstIndexOfFirstDifference; - var expectedRight = firstIndexOfFirstDifference; - var keepAugmenting = true; - - while (keepAugmenting && - IsInCopyFrameLength(left, actualRight, actual.Length) && - IsInCopyFrameLength(left, expectedRight, expected.Length)) - { - keepAugmenting = false; - - if (left > 0) - { - left--; - keepAugmenting = true; - } - - if (IsInCopyFrameLength(left, actualRight, actual.Length) && - IsInCopyFrameLength(left, expectedRight, expected.Length)) - { - if (actual.Length > actualRight) - { - actualRight++; - keepAugmenting = true; - } - - if (expected.Length > expectedRight) - { - expectedRight++; - keepAugmenting = true; - } - } - } - - actualReported = actual.Substring(left, actualRight - left); - expectedReported = expected.Substring(left, expectedRight - left); - - if (left != 0) - { - actualReported = "..." + actualReported; - expectedReported = "..." + expectedReported; - } - - if (actualRight != actual.Length || expectedRight != expected.Length) - { - actualReported = actualReported + "..."; - expectedReported = expectedReported + "..."; - } - } - - private static bool IsInCopyFrameLength(int start, int end, int max) - { - var length = end - start; - - if (start > 0) - { - length += 3; - } - - if (end < max) - { - length += 3; - } - - return length < 64; - } - - private static int GetFirstIndexOfFirstDifference(string actual, string expected) - { - for (var i = 0; i < actual.Length; i++) - { - if (expected.Length <= i || expected[i] != actual[i]) - { - return i; - } - } - - return actual.Length; - } - - private static string GetExpectedStringLengthMessage(int actual, int expected) - { - if (actual == expected) - { - return $"String lengths are both {actual}."; - } - - return $"Expected string length {actual} but was {expected}."; - } - } -} From 337840798ffcbf78b2f7fb9f18e32cb04a3ae3f3 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Tue, 1 Mar 2022 22:28:52 +0800 Subject: [PATCH 8/9] refactor --- .../Formatting/ObjectExtensionsSpecs.cs | 2 +- .../Reflection/ObjectGraphSpecs.cs | 2 - .../EnumerableExtensions.cs | 30 +++--- .../EquivalenceExtensions.cs | 29 +++--- .../Formatting/EnumerableExtensions.cs | 2 +- .../Formatting/ObjectExtensions.cs | 2 +- .../ShouldLikeExtensions.cs | 97 +++++++++++++++++++ 7 files changed, 132 insertions(+), 32 deletions(-) create mode 100644 src/Machine.Specifications.Should/ShouldLikeExtensions.cs diff --git a/src/Machine.Specifications.Should.Specs/Formatting/ObjectExtensionsSpecs.cs b/src/Machine.Specifications.Should.Specs/Formatting/ObjectExtensionsSpecs.cs index 5d3f92a2..6346cfc8 100644 --- a/src/Machine.Specifications.Should.Specs/Formatting/ObjectExtensionsSpecs.cs +++ b/src/Machine.Specifications.Should.Specs/Formatting/ObjectExtensionsSpecs.cs @@ -2,7 +2,7 @@ using System.Globalization; using System.Threading; -namespace Machine.Specifications.Should.Specs.Utility.Internal +namespace Machine.Specifications.Should.Specs.Formatting { class when_comparing_two_long_unequal_strings_with_difference_in_the_middle { diff --git a/src/Machine.Specifications.Should.Specs/Reflection/ObjectGraphSpecs.cs b/src/Machine.Specifications.Should.Specs/Reflection/ObjectGraphSpecs.cs index d33b9b9f..44b27200 100644 --- a/src/Machine.Specifications.Should.Specs/Reflection/ObjectGraphSpecs.cs +++ b/src/Machine.Specifications.Should.Specs/Reflection/ObjectGraphSpecs.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Machine.Specifications.Reflection; namespace Machine.Specifications.Should.Specs.Reflection diff --git a/src/Machine.Specifications.Should/EnumerableExtensions.cs b/src/Machine.Specifications.Should/EnumerableExtensions.cs index d59bbbae..79d733a7 100644 --- a/src/Machine.Specifications.Should/EnumerableExtensions.cs +++ b/src/Machine.Specifications.Should/EnumerableExtensions.cs @@ -23,7 +23,7 @@ public static void ShouldEachConformTo(this IEnumerable list, Expression(this IEnumerable list, IEnumerable ite @"Should contain: {0}" + Environment.NewLine + "entire list: {1}" + Environment.NewLine + "does not contain: {2}", - itemsArray.EachToUsefulString(), - listArray.EachToUsefulString(), - noContain.EachToUsefulString())); + itemsArray.Format(), + listArray.Format(), + noContain.Format())); } } @@ -72,7 +72,7 @@ public static void ShouldContain(this IEnumerable list, Expression(this IEnumerable list, IEnumerable @"Should not contain: {0}" + Environment.NewLine + "entire list: {1}" + Environment.NewLine + "does contain: {2}", - itemsArray.EachToUsefulString(), - listArray.EachToUsefulString(), - contains.EachToUsefulString())); + itemsArray.Format(), + listArray.Format(), + contains.Format())); } } @@ -124,8 +124,8 @@ public static void ShouldNotContain(this IEnumerable list, Expression(this IEnumerable list, IEnumerable if (noContain.Any() || source.Any()) { var message = string.Format(@"Should contain only: {0}" + Environment.NewLine + "entire list: {1}", - itemsArray.EachToUsefulString(), - listArray.EachToUsefulString()); + itemsArray.Format(), + listArray.Format()); if (noContain.Any()) { - message += "\ndoes not contain: " + noContain.EachToUsefulString(); + message += "\ndoes not contain: " + noContain.Format(); } if (source.Any()) { - message += "\ndoes contain but shouldn't: " + source.EachToUsefulString(); + message += "\ndoes contain but shouldn't: " + source.Format(); } throw new SpecificationException(message); diff --git a/src/Machine.Specifications.Should/EquivalenceExtensions.cs b/src/Machine.Specifications.Should/EquivalenceExtensions.cs index 0d1f8192..be3e713b 100644 --- a/src/Machine.Specifications.Should/EquivalenceExtensions.cs +++ b/src/Machine.Specifications.Should/EquivalenceExtensions.cs @@ -9,21 +9,26 @@ namespace Machine.Specifications { public static class EquivalenceExtensions { - public static void ShouldBeLike(this object obj, object expected) + public static void ShouldBeLike(this object value, object expected) { - var exceptions = ShouldBeLike(obj, expected, string.Empty, new HashSet()).ToArray(); + var exceptions = ShouldBeLike(value, expected, string.Empty, new HashSet()).ToArray(); if (exceptions.Any()) { - throw Exceptions.Specification(exceptions.Select(e => e.Message).Aggregate(string.Empty, (r, m) => r + m + Environment.NewLine + Environment.NewLine).TrimEnd()); + var message = exceptions + .Select(e => e.Message) + .Aggregate(string.Empty, (r, m) => r + m + Environment.NewLine + Environment.NewLine) + .TrimEnd(); + + throw Exceptions.Specification(message); } } - private static IEnumerable ShouldBeLike(object? obj, object? expected, string nodeName, ISet visited) + private static IEnumerable ShouldBeLike(object? value, object? expected, string nodeName, ISet visited) { // Stop at already checked -pairs to prevent infinite loops (cycles in object graphs). Additionally // this also avoids re-equality-evaluation for already compared pairs. - var tuple = new ReferentialEqualityTuple(obj, expected); + var tuple = new ReferentialEqualityTuple(value, expected); if (visited.Contains(tuple)) { @@ -35,7 +40,7 @@ private static IEnumerable ShouldBeLike(object? obj, obj var expectedNode = default(INode); var nodeType = typeof(LiteralNode); - if (obj != null && expected != null) + if (value != null && expected != null) { expectedNode = ObjectGraph.Get(expected); nodeType = expectedNode.GetType(); @@ -45,7 +50,7 @@ private static IEnumerable ShouldBeLike(object? obj, obj { try { - obj.ShouldEqual(expected); + value.ShouldEqual(expected); } catch (SpecificationException ex) { @@ -57,18 +62,18 @@ private static IEnumerable ShouldBeLike(object? obj, obj if (nodeType == typeof(SequenceNode)) { - if (obj == null) + if (value == null) { var errorMessage = ObjectExtensions.FormatErrorMessage(null, expected); return new[] { Exceptions.Specification($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; } - var actualNode = ObjectGraph.Get(obj); + var actualNode = ObjectGraph.Get(value); if (actualNode.GetType() != typeof(SequenceNode)) { - var errorMessage = $" Expected: Array or Sequence{Environment.NewLine} But was: {obj.GetType()}"; + var errorMessage = $" Expected: Array or Sequence{Environment.NewLine} But was: {value.GetType()}"; return new[] { Exceptions.Specification($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; } @@ -92,11 +97,11 @@ private static IEnumerable ShouldBeLike(object? obj, obj if (nodeType == typeof(KeyValueNode)) { - var actualNode = ObjectGraph.Get(obj!); + var actualNode = ObjectGraph.Get(value!); if (actualNode.GetType() != typeof(KeyValueNode)) { - var errorMessage = $" Expected: Class{Environment.NewLine} But was: {obj?.GetType()}"; + var errorMessage = $" Expected: Class{Environment.NewLine} But was: {value?.GetType()}"; return new[] { Exceptions.Specification($"{{0}}:{Environment.NewLine}{errorMessage}", nodeName) }; } diff --git a/src/Machine.Specifications.Should/Formatting/EnumerableExtensions.cs b/src/Machine.Specifications.Should/Formatting/EnumerableExtensions.cs index 745038ed..a1114c4a 100644 --- a/src/Machine.Specifications.Should/Formatting/EnumerableExtensions.cs +++ b/src/Machine.Specifications.Should/Formatting/EnumerableExtensions.cs @@ -6,7 +6,7 @@ namespace Machine.Specifications.Formatting { internal static class EnumerableExtensions { - public static string EachToUsefulString(this IEnumerable enumerable) + public static string Format(this IEnumerable enumerable) { var array = enumerable.ToArray(); var arrayValues = array.Select(x => x.Format().Indent()) diff --git a/src/Machine.Specifications.Should/Formatting/ObjectExtensions.cs b/src/Machine.Specifications.Should/Formatting/ObjectExtensions.cs index 37181e31..03a06dcc 100644 --- a/src/Machine.Specifications.Should/Formatting/ObjectExtensions.cs +++ b/src/Machine.Specifications.Should/Formatting/ObjectExtensions.cs @@ -29,7 +29,7 @@ public static string Format(this object? value) { var enumerable = items.Cast(); - return type + ":\r\n" + enumerable.EachToUsefulString(); + return type + ":\r\n" + enumerable.Format(); } var stringValue = value.ToString(); diff --git a/src/Machine.Specifications.Should/ShouldLikeExtensions.cs b/src/Machine.Specifications.Should/ShouldLikeExtensions.cs new file mode 100644 index 00000000..1f7dd8a9 --- /dev/null +++ b/src/Machine.Specifications.Should/ShouldLikeExtensions.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Machine.Specifications +{ + public static class ShouldLikeExtensions + { + public static void ShouldBeSimilar(this object value, object expected) + { + + } + + private static INode GetNode(object value) + { + var type = value.GetType(); + + return value switch + { + IEnumerable enumerable when type != typeof(string) => new ArrayNode(enumerable), + not null when type.IsClass && type != typeof(string) => new ObjectNode(value), + _ => new ValueNode(value) + }; + } + + private class Member + { + public Member(string name, Func value) + { + Name = name; + Value = value; + } + + public string Name { get; } + + public Func Value { get; } + } + + private interface INode + { + IEnumerable CompareTo(INode node); + } + + private class ValueNode : INode + { + private readonly object? value; + + public ValueNode(object? value) + { + this.value = value; + } + + public IEnumerable CompareTo(INode node) + { + throw new NotImplementedException(); + } + } + + private class ObjectNode : INode + { + private readonly Member[] members; + + public ObjectNode(object value) + { + var type = value.GetType(); + + var properties = type + .GetProperties() + .Where(x => x.CanRead && !x.GetGetMethod().IsStatic) + .Select(x => new Member(x.Name, () => x.GetValue(value, null))); + + var fields = type + .GetFields() + .Select(x => new Member(x.Name, () => x.GetValue(value))); + + members = properties + .Concat(fields) + .OrderBy(m => m.Name) + .ToArray(); + } + } + + private class ArrayNode : INode + { + private readonly Func[] getters; + + public ArrayNode(IEnumerable enumerable) + { + getters = enumerable + .Cast() + .Select>(x => () => x) + .ToArray(); + } + } + } +} From 1f4b5a75c9529c7b2b1fe08a8b187c386a85028e Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Sun, 21 May 2023 22:40:56 +1000 Subject: [PATCH 9/9] split extensions --- .../Reflection/ArrayTypeComparer.cs | 44 +++++++++ .../Reflection/CompareContext.cs | 10 ++ .../Reflection/INodeTypeComparer.cs | 6 ++ .../Reflection/ITypeComparer.cs | 7 ++ .../Reflection/Node.cs | 17 ++++ .../Reflection/NodeType.cs | 8 ++ .../Reflection/ObjectTypeComparer.cs | 60 ++++++++++++ .../Reflection/TypeComparer.cs | 46 +++++++++ .../Reflection/ValueTypeComparer.cs | 28 ++++++ .../ShouldLikeExtensions.cs | 97 ------------------- 10 files changed, 226 insertions(+), 97 deletions(-) create mode 100644 src/Machine.Specifications.Should/Reflection/ArrayTypeComparer.cs create mode 100644 src/Machine.Specifications.Should/Reflection/CompareContext.cs create mode 100644 src/Machine.Specifications.Should/Reflection/INodeTypeComparer.cs create mode 100644 src/Machine.Specifications.Should/Reflection/ITypeComparer.cs create mode 100644 src/Machine.Specifications.Should/Reflection/Node.cs create mode 100644 src/Machine.Specifications.Should/Reflection/NodeType.cs create mode 100644 src/Machine.Specifications.Should/Reflection/ObjectTypeComparer.cs create mode 100644 src/Machine.Specifications.Should/Reflection/TypeComparer.cs create mode 100644 src/Machine.Specifications.Should/Reflection/ValueTypeComparer.cs delete mode 100644 src/Machine.Specifications.Should/ShouldLikeExtensions.cs diff --git a/src/Machine.Specifications.Should/Reflection/ArrayTypeComparer.cs b/src/Machine.Specifications.Should/Reflection/ArrayTypeComparer.cs new file mode 100644 index 00000000..2594e1a8 --- /dev/null +++ b/src/Machine.Specifications.Should/Reflection/ArrayTypeComparer.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections; +using System.Linq; + +namespace Machine.Specifications.Reflection; + +public class ArrayTypeComparer : INodeTypeComparer +{ + private readonly ITypeComparer comparer; + + public ArrayTypeComparer(ITypeComparer comparer) + { + this.comparer = comparer; + } + + public NodeType Type => NodeType.Array; + + public void Compare(CompareContext context, Node node) + { + if (node.Value is IEnumerable value && node.Expected is IEnumerable expected) + { + var values = value.Cast().ToArray(); + var expectedValues = expected.Cast().ToArray(); + + if (values.Length != expectedValues.Length) + { + var errorMessage = string.Format( + " Expected: Sequence length of {1}{0} But was: {2}", + Environment.NewLine, + expectedValues.Length, + values.Length); + + context.Exceptions.Add(Exceptions.Specification($"{{0}}:{Environment.NewLine}{errorMessage}", node.Name)); + } + else + { + for (var i = 0; i < values.Length; i++) + { + comparer.Compare(context, new Node($"{node.Name}[{i}]", values[i], expectedValues[i])); + } + } + } + } +} diff --git a/src/Machine.Specifications.Should/Reflection/CompareContext.cs b/src/Machine.Specifications.Should/Reflection/CompareContext.cs new file mode 100644 index 00000000..7648af35 --- /dev/null +++ b/src/Machine.Specifications.Should/Reflection/CompareContext.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Machine.Specifications.Reflection; + +public class CompareContext +{ + public HashSet<(object, object)> Visited { get; } = new(); + + public List Exceptions { get; } = new(); +} diff --git a/src/Machine.Specifications.Should/Reflection/INodeTypeComparer.cs b/src/Machine.Specifications.Should/Reflection/INodeTypeComparer.cs new file mode 100644 index 00000000..c8c28679 --- /dev/null +++ b/src/Machine.Specifications.Should/Reflection/INodeTypeComparer.cs @@ -0,0 +1,6 @@ +namespace Machine.Specifications.Reflection; + +public interface INodeTypeComparer : ITypeComparer +{ + NodeType Type { get; } +} diff --git a/src/Machine.Specifications.Should/Reflection/ITypeComparer.cs b/src/Machine.Specifications.Should/Reflection/ITypeComparer.cs new file mode 100644 index 00000000..74cdbc73 --- /dev/null +++ b/src/Machine.Specifications.Should/Reflection/ITypeComparer.cs @@ -0,0 +1,7 @@ +namespace Machine.Specifications.Reflection +{ + public interface ITypeComparer + { + void Compare(CompareContext context, Node node); + } +} diff --git a/src/Machine.Specifications.Should/Reflection/Node.cs b/src/Machine.Specifications.Should/Reflection/Node.cs new file mode 100644 index 00000000..54852547 --- /dev/null +++ b/src/Machine.Specifications.Should/Reflection/Node.cs @@ -0,0 +1,17 @@ +namespace Machine.Specifications.Reflection; + +public class Node +{ + public Node(string name, object value, object expected) + { + Name = name; + Value = value; + Expected = expected; + } + + public string Name { get; } + + public object Value { get; } + + public object Expected { get; } +} diff --git a/src/Machine.Specifications.Should/Reflection/NodeType.cs b/src/Machine.Specifications.Should/Reflection/NodeType.cs new file mode 100644 index 00000000..0b89f4a9 --- /dev/null +++ b/src/Machine.Specifications.Should/Reflection/NodeType.cs @@ -0,0 +1,8 @@ +namespace Machine.Specifications.Reflection; + +public enum NodeType +{ + Object, + Array, + Value +} diff --git a/src/Machine.Specifications.Should/Reflection/ObjectTypeComparer.cs b/src/Machine.Specifications.Should/Reflection/ObjectTypeComparer.cs new file mode 100644 index 00000000..d9ded5e5 --- /dev/null +++ b/src/Machine.Specifications.Should/Reflection/ObjectTypeComparer.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Machine.Specifications.Formatting; + +namespace Machine.Specifications.Reflection; + +public class ObjectTypeComparer : INodeTypeComparer +{ + private readonly ITypeComparer comparer; + + public ObjectTypeComparer(ITypeComparer comparer) + { + this.comparer = comparer; + } + + public NodeType Type => NodeType.Object; + + public void Compare(CompareContext context, Node node) + { + var valueMembers = GetMembers(node.Value).ToArray(); + var expectedMembers = GetMembers(node.Expected); + + foreach (var (name, expectedValue) in expectedMembers) + { + var fullName = string.IsNullOrEmpty(node.Name) + ? name + : $"{node.Name}.{name}"; + + var actualMember = valueMembers.SingleOrDefault(k => k.Item1 == name); + + if (actualMember == null) + { + var errorMessage = string.Format(" Expected: {1}{0} But was: Not Defined", Environment.NewLine, expectedValue.Format()); + + context.Exceptions.Add(Exceptions.Specification($"{{0}}:{Environment.NewLine}{errorMessage}", fullName)); + } + else + { + comparer.Compare(context, new Node(fullName, actualMember.Item2, expectedValue)); + } + } + } + + private IEnumerable> GetMembers(object value) + { + var type = value.GetType(); + + var properties = type + .GetProperties() + .Where(x => x.CanRead && !x.GetGetMethod().IsStatic) + .Select(x => Tuple.Create(x.Name, x.GetValue(value, null))); + + var fields = type + .GetFields() + .Select(x => Tuple.Create(x.Name, x.GetValue(value))); + + return properties.Concat(fields).OrderBy(m => m.Item1); + } +} diff --git a/src/Machine.Specifications.Should/Reflection/TypeComparer.cs b/src/Machine.Specifications.Should/Reflection/TypeComparer.cs new file mode 100644 index 00000000..bffd7555 --- /dev/null +++ b/src/Machine.Specifications.Should/Reflection/TypeComparer.cs @@ -0,0 +1,46 @@ +using System.Collections; +using System.Linq; + +namespace Machine.Specifications.Reflection; + +public class TypeComparer : ITypeComparer +{ + private readonly INodeTypeComparer[] comparers; + + public TypeComparer() + { + comparers = new INodeTypeComparer[] + { + new ObjectTypeComparer(this), + new ArrayTypeComparer(this), + new ValueTypeComparer() + }; + } + + public void Compare(CompareContext context, Node node) + { + var current = (node.Value, node.Expected); + + if (context.Visited.Add(current)) + { + var valueType = GetNodeType(node.Value); + var expectedType = GetNodeType(node.Expected); + + var comparer = comparers.First(x => x.Type == valueType); + + comparer.Compare(context, node); + } + } + + private NodeType GetNodeType(object value) + { + var type = value.GetType(); + + return value switch + { + IEnumerable when type != typeof(string) => NodeType.Array, + not null when type.IsClass && type != typeof(string) => NodeType.Object, + _ => NodeType.Value + }; + } +} diff --git a/src/Machine.Specifications.Should/Reflection/ValueTypeComparer.cs b/src/Machine.Specifications.Should/Reflection/ValueTypeComparer.cs new file mode 100644 index 00000000..38631464 --- /dev/null +++ b/src/Machine.Specifications.Should/Reflection/ValueTypeComparer.cs @@ -0,0 +1,28 @@ +using System; + +namespace Machine.Specifications.Reflection; + +public class ValueTypeComparer : INodeTypeComparer +{ + public NodeType Type => NodeType.Value; + + public void Compare(CompareContext context, Node node) + { + try + { + try + { + node.Value.ShouldEqual(node.Expected); + } + catch (SpecificationException ex) + { + context.Exceptions.Add(Exceptions.Specification($"{{0}}:{Environment.NewLine}{ex.Message}", node.Name)); + } + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } +} diff --git a/src/Machine.Specifications.Should/ShouldLikeExtensions.cs b/src/Machine.Specifications.Should/ShouldLikeExtensions.cs deleted file mode 100644 index 1f7dd8a9..00000000 --- a/src/Machine.Specifications.Should/ShouldLikeExtensions.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace Machine.Specifications -{ - public static class ShouldLikeExtensions - { - public static void ShouldBeSimilar(this object value, object expected) - { - - } - - private static INode GetNode(object value) - { - var type = value.GetType(); - - return value switch - { - IEnumerable enumerable when type != typeof(string) => new ArrayNode(enumerable), - not null when type.IsClass && type != typeof(string) => new ObjectNode(value), - _ => new ValueNode(value) - }; - } - - private class Member - { - public Member(string name, Func value) - { - Name = name; - Value = value; - } - - public string Name { get; } - - public Func Value { get; } - } - - private interface INode - { - IEnumerable CompareTo(INode node); - } - - private class ValueNode : INode - { - private readonly object? value; - - public ValueNode(object? value) - { - this.value = value; - } - - public IEnumerable CompareTo(INode node) - { - throw new NotImplementedException(); - } - } - - private class ObjectNode : INode - { - private readonly Member[] members; - - public ObjectNode(object value) - { - var type = value.GetType(); - - var properties = type - .GetProperties() - .Where(x => x.CanRead && !x.GetGetMethod().IsStatic) - .Select(x => new Member(x.Name, () => x.GetValue(value, null))); - - var fields = type - .GetFields() - .Select(x => new Member(x.Name, () => x.GetValue(value))); - - members = properties - .Concat(fields) - .OrderBy(m => m.Name) - .ToArray(); - } - } - - private class ArrayNode : INode - { - private readonly Func[] getters; - - public ArrayNode(IEnumerable enumerable) - { - getters = enumerable - .Cast() - .Select>(x => () => x) - .ToArray(); - } - } - } -}