/src/NUnit/framework/Constraints/NUnitEqualityComparer.cs

# · C# · 330 lines · 208 code · 61 blank · 61 comment · 75 complexity · 3dbfaaa6303b0fa899eede36de1dcade MD5 · raw file

  1. // ****************************************************************
  2. // Copyright 2009, Charlie Poole
  3. // This is free software licensed under the NUnit license. You may
  4. // obtain a copy of the license at http://nunit.org
  5. // ****************************************************************
  6. using System;
  7. using System.IO;
  8. using System.Collections;
  9. #if NET_2_0
  10. using System.Collections.Generic;
  11. #endif
  12. namespace NUnit.Framework.Constraints
  13. {
  14. /// <summary>
  15. /// NUnitEqualityComparer encapsulates NUnit's handling of
  16. /// equality tests between objects.
  17. /// </summary>
  18. public class NUnitEqualityComparer
  19. {
  20. #region Static and Instance Fields
  21. /// <summary>
  22. /// If true, all string comparisons will ignore case
  23. /// </summary>
  24. private bool caseInsensitive;
  25. /// <summary>
  26. /// If true, arrays will be treated as collections, allowing
  27. /// those of different dimensions to be compared
  28. /// </summary>
  29. private bool compareAsCollection;
  30. /// <summary>
  31. /// If non-zero, equality comparisons within the specified
  32. /// tolerance will succeed.
  33. /// </summary>
  34. private Tolerance tolerance = Tolerance.Empty;
  35. /// <summary>
  36. /// Comparison object used in comparisons for some constraints.
  37. /// </summary>
  38. private EqualityAdapter externalComparer;
  39. private ArrayList failurePoints;
  40. private static readonly int BUFFER_SIZE = 4096;
  41. #endregion
  42. #region Properties
  43. /// <summary>
  44. /// Returns the default NUnitEqualityComparer
  45. /// </summary>
  46. public static NUnitEqualityComparer Default
  47. {
  48. get { return new NUnitEqualityComparer(); }
  49. }
  50. /// <summary>
  51. /// Gets and sets a flag indicating whether case should
  52. /// be ignored in determining equality.
  53. /// </summary>
  54. public bool IgnoreCase
  55. {
  56. get { return caseInsensitive; }
  57. set { caseInsensitive = value; }
  58. }
  59. /// <summary>
  60. /// Gets and sets a flag indicating that arrays should be
  61. /// compared as collections, without regard to their shape.
  62. /// </summary>
  63. public bool CompareAsCollection
  64. {
  65. get { return compareAsCollection; }
  66. set { compareAsCollection = value; }
  67. }
  68. /// <summary>
  69. /// Gets and sets an external comparer to be used to
  70. /// test for equality. It is applied to members of
  71. /// collections, in place of NUnit's own logic.
  72. /// </summary>
  73. public EqualityAdapter ExternalComparer
  74. {
  75. get { return externalComparer; }
  76. set { externalComparer = value; }
  77. }
  78. /// <summary>
  79. /// Gets and sets a tolerance used to compare objects of
  80. /// certin types.
  81. /// </summary>
  82. public Tolerance Tolerance
  83. {
  84. get { return tolerance; }
  85. set { tolerance = value; }
  86. }
  87. /// <summary>
  88. /// Gets the list of failure points for the last Match performed.
  89. /// </summary>
  90. public IList FailurePoints
  91. {
  92. get { return failurePoints; }
  93. }
  94. #endregion
  95. #region Public Methods
  96. /// <summary>
  97. /// Compares two objects for equality.
  98. /// </summary>
  99. public bool ObjectsEqual(object x, object y)
  100. {
  101. this.failurePoints = new ArrayList();
  102. if (x == null && y == null)
  103. return true;
  104. if (x == null || y == null)
  105. return false;
  106. if (object.ReferenceEquals(x, y))
  107. return true;
  108. Type xType = x.GetType();
  109. Type yType = y.GetType();
  110. if (xType.IsArray && yType.IsArray && !compareAsCollection)
  111. return ArraysEqual((Array)x, (Array)y);
  112. if (x is IDictionary && y is IDictionary)
  113. return DictionariesEqual((IDictionary)x, (IDictionary)y);
  114. if (x is ICollection && y is ICollection)
  115. return CollectionsEqual((ICollection)x, (ICollection)y);
  116. if (x is IEnumerable && y is IEnumerable && !(x is string && y is string))
  117. return EnumerablesEqual((IEnumerable)x, (IEnumerable)y);
  118. if (externalComparer != null)
  119. return externalComparer.ObjectsEqual(x, y);
  120. if (x is string && y is string)
  121. return StringsEqual((string)x, (string)y);
  122. if (x is Stream && y is Stream)
  123. return StreamsEqual((Stream)x, (Stream)y);
  124. if (x is DirectoryInfo && y is DirectoryInfo)
  125. return DirectoriesEqual((DirectoryInfo)x, (DirectoryInfo)y);
  126. if (Numerics.IsNumericType(x) && Numerics.IsNumericType(y))
  127. return Numerics.AreEqual(x, y, ref tolerance);
  128. if (tolerance != null && tolerance.Value is TimeSpan)
  129. {
  130. TimeSpan amount = (TimeSpan)tolerance.Value;
  131. if (x is DateTime && y is DateTime)
  132. return ((DateTime)x - (DateTime)y).Duration() <= amount;
  133. if (x is TimeSpan && y is TimeSpan)
  134. return ((TimeSpan)x - (TimeSpan)y).Duration() <= amount;
  135. }
  136. return x.Equals(y);
  137. }
  138. #endregion
  139. #region Helper Methods
  140. /// <summary>
  141. /// Helper method to compare two arrays
  142. /// </summary>
  143. private bool ArraysEqual(Array x, Array y)
  144. {
  145. int rank = x.Rank;
  146. if (rank != y.Rank)
  147. return false;
  148. for (int r = 1; r < rank; r++)
  149. if (x.GetLength(r) != y.GetLength(r))
  150. return false;
  151. return CollectionsEqual((ICollection)x, (ICollection)y);
  152. }
  153. private bool DictionariesEqual(IDictionary x, IDictionary y)
  154. {
  155. if (x.Count != y.Count)
  156. return false;
  157. CollectionTally tally = new CollectionTally(this, x.Keys);
  158. if (!tally.TryRemove(y.Keys) || tally.Count > 0)
  159. return false;
  160. foreach (object key in x.Keys)
  161. if (!ObjectsEqual(x[key], y[key]))
  162. return false;
  163. return true;
  164. }
  165. private bool CollectionsEqual(ICollection x, ICollection y)
  166. {
  167. IEnumerator expectedEnum = x.GetEnumerator();
  168. IEnumerator actualEnum = y.GetEnumerator();
  169. int count;
  170. for (count = 0; expectedEnum.MoveNext() && actualEnum.MoveNext(); count++)
  171. {
  172. if (!ObjectsEqual(expectedEnum.Current, actualEnum.Current))
  173. break;
  174. }
  175. if (count == x.Count && count == y.Count)
  176. return true;
  177. failurePoints.Insert(0, count);
  178. return false;
  179. }
  180. private bool StringsEqual(string x, string y)
  181. {
  182. string s1 = caseInsensitive ? x.ToLower() : x;
  183. string s2 = caseInsensitive ? y.ToLower() : y;
  184. return s1.Equals(s2);
  185. }
  186. private bool EnumerablesEqual(IEnumerable x, IEnumerable y)
  187. {
  188. IEnumerator expectedEnum = x.GetEnumerator();
  189. IEnumerator actualEnum = y.GetEnumerator();
  190. int count = 0;
  191. for (; ; )
  192. {
  193. bool expectedHasData = expectedEnum.MoveNext();
  194. bool actualHasData = actualEnum.MoveNext();
  195. if (!expectedHasData && !actualHasData)
  196. return true;
  197. if (expectedHasData != actualHasData ||
  198. !ObjectsEqual(expectedEnum.Current, actualEnum.Current))
  199. {
  200. failurePoints.Insert(0, count);
  201. return false;
  202. }
  203. }
  204. }
  205. /// <summary>
  206. /// Method to compare two DirectoryInfo objects
  207. /// </summary>
  208. /// <param name="x">first directory to compare</param>
  209. /// <param name="y">second directory to compare</param>
  210. /// <returns>true if equivalent, false if not</returns>
  211. private bool DirectoriesEqual(DirectoryInfo x, DirectoryInfo y)
  212. {
  213. // Do quick compares first
  214. if (x.Attributes != y.Attributes ||
  215. x.CreationTime != y.CreationTime ||
  216. x.LastAccessTime != y.LastAccessTime)
  217. {
  218. return false;
  219. }
  220. // TODO: Find a cleaner way to do this
  221. return new SamePathConstraint(x.FullName).Matches(y.FullName);
  222. }
  223. private bool StreamsEqual(Stream x, Stream y)
  224. {
  225. if (x == y) return true;
  226. if (!x.CanRead)
  227. throw new ArgumentException("Stream is not readable", "expected");
  228. if (!y.CanRead)
  229. throw new ArgumentException("Stream is not readable", "actual");
  230. if (!x.CanSeek)
  231. throw new ArgumentException("Stream is not seekable", "expected");
  232. if (!y.CanSeek)
  233. throw new ArgumentException("Stream is not seekable", "actual");
  234. if (x.Length != y.Length) return false;
  235. byte[] bufferExpected = new byte[BUFFER_SIZE];
  236. byte[] bufferActual = new byte[BUFFER_SIZE];
  237. BinaryReader binaryReaderExpected = new BinaryReader(x);
  238. BinaryReader binaryReaderActual = new BinaryReader(y);
  239. long expectedPosition = x.Position;
  240. long actualPosition = y.Position;
  241. try
  242. {
  243. binaryReaderExpected.BaseStream.Seek(0, SeekOrigin.Begin);
  244. binaryReaderActual.BaseStream.Seek(0, SeekOrigin.Begin);
  245. for (long readByte = 0; readByte < x.Length; readByte += BUFFER_SIZE)
  246. {
  247. binaryReaderExpected.Read(bufferExpected, 0, BUFFER_SIZE);
  248. binaryReaderActual.Read(bufferActual, 0, BUFFER_SIZE);
  249. for (int count = 0; count < BUFFER_SIZE; ++count)
  250. {
  251. if (bufferExpected[count] != bufferActual[count])
  252. {
  253. failurePoints.Insert(0, readByte + count);
  254. //FailureMessage.WriteLine("\tIndex : {0}", readByte + count);
  255. return false;
  256. }
  257. }
  258. }
  259. }
  260. finally
  261. {
  262. x.Position = expectedPosition;
  263. y.Position = actualPosition;
  264. }
  265. return true;
  266. }
  267. #endregion
  268. }
  269. }