XmlAssert.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Xml.Linq;
  7. using Xunit;
  8. namespace Microsoft.AspNetCore.DataProtection
  9. {
  10. /// <summary>
  11. /// Helpful XML-based assertions.
  12. /// </summary>
  13. public static class XmlAssert
  14. {
  15. public static readonly IEqualityComparer<XNode> EqualityComparer = new CallbackBasedEqualityComparer<XNode>(Core.AreEqual);
  16. /// <summary>
  17. /// Asserts that a <see cref="string"/> and an <see cref="XElement"/> are semantically equivalent.
  18. /// </summary>
  19. public static void Equal(string expected, XElement actual)
  20. {
  21. Assert.NotNull(expected);
  22. Assert.NotNull(actual);
  23. Equal(XElement.Parse(expected), actual);
  24. }
  25. /// <summary>
  26. /// Asserts that two <see cref="XElement"/> instances are semantically equivalent.
  27. /// </summary>
  28. public static void Equal(XElement expected, XElement actual)
  29. {
  30. Assert.NotNull(expected);
  31. Assert.NotNull(actual);
  32. if (!Core.AreEqual(expected, actual))
  33. {
  34. Assert.True(false,
  35. "Expected element:" + Environment.NewLine
  36. + expected.ToString() + Environment.NewLine
  37. + "Actual element:" + Environment.NewLine
  38. + actual.ToString());
  39. }
  40. }
  41. private static class Core
  42. {
  43. private static readonly IEqualityComparer<XAttribute> AttributeEqualityComparer = new CallbackBasedEqualityComparer<XAttribute>(AreEqual);
  44. private static bool AreEqual(XElement expected, XElement actual)
  45. {
  46. return expected.Name == actual.Name
  47. && AreEqual(expected.Attributes(), actual.Attributes())
  48. && AreEqual(expected.Nodes(), actual.Nodes());
  49. }
  50. private static bool AreEqual(IEnumerable<XNode> expected, IEnumerable<XNode> actual)
  51. {
  52. List<XNode> filteredExpected = expected.Where(ShouldIncludeNodeDuringComparison).ToList();
  53. List<XNode> filteredActual = actual.Where(ShouldIncludeNodeDuringComparison).ToList();
  54. return filteredExpected.SequenceEqual(filteredActual, EqualityComparer);
  55. }
  56. internal static bool AreEqual(XNode expected, XNode actual)
  57. {
  58. if (expected is XText && actual is XText)
  59. {
  60. return AreEqual((XText)expected, (XText)actual);
  61. }
  62. else if (expected is XElement && actual is XElement)
  63. {
  64. return AreEqual((XElement)expected, (XElement)actual);
  65. }
  66. else
  67. {
  68. return false;
  69. }
  70. }
  71. private static bool AreEqual(XText expected, XText actual)
  72. {
  73. return expected.Value == actual.Value;
  74. }
  75. private static bool AreEqual(IEnumerable<XAttribute> expected, IEnumerable<XAttribute> actual)
  76. {
  77. List<XAttribute> orderedExpected = expected
  78. .Where(ShouldIncludeAttributeDuringComparison)
  79. .OrderBy(attr => attr.Name.ToString())
  80. .ToList();
  81. List<XAttribute> orderedActual = actual
  82. .Where(ShouldIncludeAttributeDuringComparison)
  83. .OrderBy(attr => attr.Name.ToString())
  84. .ToList();
  85. return orderedExpected.SequenceEqual(orderedActual, AttributeEqualityComparer);
  86. }
  87. private static bool AreEqual(XAttribute expected, XAttribute actual)
  88. {
  89. return expected.Name == actual.Name
  90. && expected.Value == actual.Value;
  91. }
  92. private static bool ShouldIncludeAttributeDuringComparison(XAttribute attribute)
  93. {
  94. // exclude 'xmlns' attributes since they're already considered in the
  95. // actual element and attribute names
  96. return attribute.Name != (XName)"xmlns"
  97. && attribute.Name.Namespace != XNamespace.Xmlns;
  98. }
  99. private static bool ShouldIncludeNodeDuringComparison(XNode node)
  100. {
  101. if (node is XComment)
  102. {
  103. return false; // not contextually relevant
  104. }
  105. if (node is XText /* includes XCData */ || node is XElement)
  106. {
  107. return true; // relevant
  108. }
  109. throw new NotSupportedException(String.Format("Node of type '{0}' is not supported.", node.GetType().Name));
  110. }
  111. }
  112. private sealed class CallbackBasedEqualityComparer<T> : IEqualityComparer<T>
  113. {
  114. private readonly Func<T, T, bool> _equalityCheck;
  115. public CallbackBasedEqualityComparer(Func<T, T, bool> equalityCheck)
  116. {
  117. _equalityCheck = equalityCheck;
  118. }
  119. public bool Equals(T x, T y)
  120. {
  121. return _equalityCheck(x, y);
  122. }
  123. public int GetHashCode(T obj)
  124. {
  125. return obj.ToString().GetHashCode();
  126. }
  127. }
  128. }
  129. }