PageRenderTime 42ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/src/NUnit/framework/Constraints/EqualConstraint.cs

#
C# | 510 lines | 303 code | 56 blank | 151 comment | 46 complexity | dc4dddf6cd4d86229be538817788ead4 MD5 | raw file
Possible License(s): GPL-2.0
  1. // ****************************************************************
  2. // Copyright 2007, 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. /// EqualConstraint is able to compare an actual value with the
  16. /// expected value provided in its constructor. Two objects are
  17. /// considered equal if both are null, or if both have the same
  18. /// value. NUnit has special semantics for some object types.
  19. /// </summary>
  20. public class EqualConstraint : Constraint
  21. {
  22. #region Static and Instance Fields
  23. private readonly object expected;
  24. /// <summary>
  25. /// If true, strings in error messages will be clipped
  26. /// </summary>
  27. private bool clipStrings = true;
  28. /// <summary>
  29. /// NUnitEqualityComparer used to test equality.
  30. /// </summary>
  31. private NUnitEqualityComparer comparer = new NUnitEqualityComparer();
  32. #region Message Strings
  33. private static readonly string StringsDiffer_1 =
  34. "String lengths are both {0}. Strings differ at index {1}.";
  35. private static readonly string StringsDiffer_2 =
  36. "Expected string length {0} but was {1}. Strings differ at index {2}.";
  37. private static readonly string StreamsDiffer_1 =
  38. "Stream lengths are both {0}. Streams differ at offset {1}.";
  39. private static readonly string StreamsDiffer_2 =
  40. "Expected Stream length {0} but was {1}.";// Streams differ at offset {2}.";
  41. private static readonly string CollectionType_1 =
  42. "Expected and actual are both {0}";
  43. private static readonly string CollectionType_2 =
  44. "Expected is {0}, actual is {1}";
  45. private static readonly string ValuesDiffer_1 =
  46. "Values differ at index {0}";
  47. private static readonly string ValuesDiffer_2 =
  48. "Values differ at expected index {0}, actual index {1}";
  49. #endregion
  50. #endregion
  51. #region Constructor
  52. /// <summary>
  53. /// Initializes a new instance of the <see cref="EqualConstraint"/> class.
  54. /// </summary>
  55. /// <param name="expected">The expected value.</param>
  56. public EqualConstraint(object expected) : base(expected)
  57. {
  58. this.expected = expected;
  59. }
  60. #endregion
  61. #region Constraint Modifiers
  62. /// <summary>
  63. /// Flag the constraint to ignore case and return self.
  64. /// </summary>
  65. public EqualConstraint IgnoreCase
  66. {
  67. get
  68. {
  69. comparer.IgnoreCase = true;
  70. return this;
  71. }
  72. }
  73. /// <summary>
  74. /// Flag the constraint to suppress string clipping
  75. /// and return self.
  76. /// </summary>
  77. public EqualConstraint NoClip
  78. {
  79. get
  80. {
  81. clipStrings = false;
  82. return this;
  83. }
  84. }
  85. /// <summary>
  86. /// Flag the constraint to compare arrays as collections
  87. /// and return self.
  88. /// </summary>
  89. public EqualConstraint AsCollection
  90. {
  91. get
  92. {
  93. comparer.CompareAsCollection = true;
  94. return this;
  95. }
  96. }
  97. /// <summary>
  98. /// Flag the constraint to use a tolerance when determining equality.
  99. /// </summary>
  100. /// <param name="amount">Tolerance value to be used</param>
  101. /// <returns>Self.</returns>
  102. public EqualConstraint Within(object amount)
  103. {
  104. if (!comparer.Tolerance.IsEmpty)
  105. throw new InvalidOperationException("Within modifier may appear only once in a constraint expression");
  106. comparer.Tolerance = new Tolerance(amount);
  107. return this;
  108. }
  109. /// <summary>
  110. /// Switches the .Within() modifier to interpret its tolerance as
  111. /// a distance in representable values (see remarks).
  112. /// </summary>
  113. /// <returns>Self.</returns>
  114. /// <remarks>
  115. /// Ulp stands for "unit in the last place" and describes the minimum
  116. /// amount a given value can change. For any integers, an ulp is 1 whole
  117. /// digit. For floating point values, the accuracy of which is better
  118. /// for smaller numbers and worse for larger numbers, an ulp depends
  119. /// on the size of the number. Using ulps for comparison of floating
  120. /// point results instead of fixed tolerances is safer because it will
  121. /// automatically compensate for the added inaccuracy of larger numbers.
  122. /// </remarks>
  123. public EqualConstraint Ulps
  124. {
  125. get
  126. {
  127. comparer.Tolerance = comparer.Tolerance.Ulps;
  128. return this;
  129. }
  130. }
  131. /// <summary>
  132. /// Switches the .Within() modifier to interpret its tolerance as
  133. /// a percentage that the actual values is allowed to deviate from
  134. /// the expected value.
  135. /// </summary>
  136. /// <returns>Self</returns>
  137. public EqualConstraint Percent
  138. {
  139. get
  140. {
  141. comparer.Tolerance = comparer.Tolerance.Percent;
  142. return this;
  143. }
  144. }
  145. /// <summary>
  146. /// Causes the tolerance to be interpreted as a TimeSpan in days.
  147. /// </summary>
  148. /// <returns>Self</returns>
  149. public EqualConstraint Days
  150. {
  151. get
  152. {
  153. comparer.Tolerance = comparer.Tolerance.Days;
  154. return this;
  155. }
  156. }
  157. /// <summary>
  158. /// Causes the tolerance to be interpreted as a TimeSpan in hours.
  159. /// </summary>
  160. /// <returns>Self</returns>
  161. public EqualConstraint Hours
  162. {
  163. get
  164. {
  165. comparer.Tolerance = comparer.Tolerance.Hours;
  166. return this;
  167. }
  168. }
  169. /// <summary>
  170. /// Causes the tolerance to be interpreted as a TimeSpan in minutes.
  171. /// </summary>
  172. /// <returns>Self</returns>
  173. public EqualConstraint Minutes
  174. {
  175. get
  176. {
  177. comparer.Tolerance = comparer.Tolerance.Minutes;
  178. return this;
  179. }
  180. }
  181. /// <summary>
  182. /// Causes the tolerance to be interpreted as a TimeSpan in seconds.
  183. /// </summary>
  184. /// <returns>Self</returns>
  185. public EqualConstraint Seconds
  186. {
  187. get
  188. {
  189. comparer.Tolerance = comparer.Tolerance.Seconds;
  190. return this;
  191. }
  192. }
  193. /// <summary>
  194. /// Causes the tolerance to be interpreted as a TimeSpan in milliseconds.
  195. /// </summary>
  196. /// <returns>Self</returns>
  197. public EqualConstraint Milliseconds
  198. {
  199. get
  200. {
  201. comparer.Tolerance = comparer.Tolerance.Milliseconds;
  202. return this;
  203. }
  204. }
  205. /// <summary>
  206. /// Causes the tolerance to be interpreted as a TimeSpan in clock ticks.
  207. /// </summary>
  208. /// <returns>Self</returns>
  209. public EqualConstraint Ticks
  210. {
  211. get
  212. {
  213. comparer.Tolerance = comparer.Tolerance.Ticks;
  214. return this;
  215. }
  216. }
  217. /// <summary>
  218. /// Flag the constraint to use the supplied IComparer object.
  219. /// </summary>
  220. /// <param name="comparer">The IComparer object to use.</param>
  221. /// <returns>Self.</returns>
  222. [Obsolete("Replace with 'Using'")]
  223. public EqualConstraint Comparer(IComparer comparer)
  224. {
  225. return Using(comparer);
  226. }
  227. /// <summary>
  228. /// Flag the constraint to use the supplied IComparer object.
  229. /// </summary>
  230. /// <param name="comparer">The IComparer object to use.</param>
  231. /// <returns>Self.</returns>
  232. public EqualConstraint Using(IComparer comparer)
  233. {
  234. this.comparer.ExternalComparer = EqualityAdapter.For(comparer);
  235. return this;
  236. }
  237. #if NET_2_0
  238. /// <summary>
  239. /// Flag the constraint to use the supplied IComparer object.
  240. /// </summary>
  241. /// <param name="comparer">The IComparer object to use.</param>
  242. /// <returns>Self.</returns>
  243. public EqualConstraint Using<T>(IComparer<T> comparer)
  244. {
  245. this.comparer.ExternalComparer = EqualityAdapter.For( comparer );
  246. return this;
  247. }
  248. /// <summary>
  249. /// Flag the constraint to use the supplied Comparison object.
  250. /// </summary>
  251. /// <param name="comparer">The IComparer object to use.</param>
  252. /// <returns>Self.</returns>
  253. public EqualConstraint Using<T>(Comparison<T> comparer)
  254. {
  255. this.comparer.ExternalComparer = EqualityAdapter.For( comparer );
  256. return this;
  257. }
  258. /// <summary>
  259. /// Flag the constraint to use the supplied IEqualityComparer object.
  260. /// </summary>
  261. /// <param name="comparer">The IComparer object to use.</param>
  262. /// <returns>Self.</returns>
  263. public EqualConstraint Using(IEqualityComparer comparer)
  264. {
  265. this.comparer.ExternalComparer = EqualityAdapter.For(comparer);
  266. return this;
  267. }
  268. /// <summary>
  269. /// Flag the constraint to use the supplied IEqualityComparer object.
  270. /// </summary>
  271. /// <param name="comparer">The IComparer object to use.</param>
  272. /// <returns>Self.</returns>
  273. public EqualConstraint Using<T>(IEqualityComparer<T> comparer)
  274. {
  275. this.comparer.ExternalComparer = EqualityAdapter.For(comparer);
  276. return this;
  277. }
  278. #endif
  279. #endregion
  280. #region Public Methods
  281. /// <summary>
  282. /// Test whether the constraint is satisfied by a given value
  283. /// </summary>
  284. /// <param name="actual">The value to be tested</param>
  285. /// <returns>True for success, false for failure</returns>
  286. public override bool Matches(object actual)
  287. {
  288. this.actual = actual;
  289. return comparer.ObjectsEqual(expected, actual);
  290. }
  291. /// <summary>
  292. /// Write a failure message. Overridden to provide custom
  293. /// failure messages for EqualConstraint.
  294. /// </summary>
  295. /// <param name="writer">The MessageWriter to write to</param>
  296. public override void WriteMessageTo(MessageWriter writer)
  297. {
  298. DisplayDifferences(writer, expected, actual, 0);
  299. }
  300. /// <summary>
  301. /// Write description of this constraint
  302. /// </summary>
  303. /// <param name="writer">The MessageWriter to write to</param>
  304. public override void WriteDescriptionTo(MessageWriter writer)
  305. {
  306. writer.WriteExpectedValue( expected );
  307. if (comparer.Tolerance != null && !comparer.Tolerance.IsEmpty)
  308. {
  309. writer.WriteConnector("+/-");
  310. writer.WriteExpectedValue(comparer.Tolerance.Value);
  311. }
  312. if ( comparer.IgnoreCase )
  313. writer.WriteModifier("ignoring case");
  314. }
  315. private void DisplayDifferences(MessageWriter writer, object expected, object actual, int depth)
  316. {
  317. if (expected is string && actual is string)
  318. DisplayStringDifferences(writer, (string)expected, (string)actual);
  319. else if (expected is ICollection && actual is ICollection)
  320. DisplayCollectionDifferences(writer, (ICollection)expected, (ICollection)actual, depth);
  321. else if (expected is Stream && actual is Stream)
  322. DisplayStreamDifferences(writer, (Stream)expected, (Stream)actual, depth);
  323. else if (comparer.Tolerance != null)
  324. writer.DisplayDifferences(expected, actual, comparer.Tolerance);
  325. else
  326. writer.DisplayDifferences(expected, actual);
  327. }
  328. #endregion
  329. #region DisplayStringDifferences
  330. private void DisplayStringDifferences(MessageWriter writer, string expected, string actual)
  331. {
  332. int mismatch = MsgUtils.FindMismatchPosition(expected, actual, 0, comparer.IgnoreCase);
  333. if (expected.Length == actual.Length)
  334. writer.WriteMessageLine(StringsDiffer_1, expected.Length, mismatch);
  335. else
  336. writer.WriteMessageLine(StringsDiffer_2, expected.Length, actual.Length, mismatch);
  337. writer.DisplayStringDifferences(expected, actual, mismatch, comparer.IgnoreCase, clipStrings);
  338. }
  339. #endregion
  340. #region DisplayStreamDifferences
  341. private void DisplayStreamDifferences(MessageWriter writer, Stream expected, Stream actual, int depth)
  342. {
  343. if ( expected.Length == actual.Length )
  344. {
  345. long offset = (long)comparer.FailurePoints[depth];
  346. writer.WriteMessageLine(StreamsDiffer_1, expected.Length, offset);
  347. }
  348. else
  349. writer.WriteMessageLine(StreamsDiffer_2, expected.Length, actual.Length);
  350. }
  351. #endregion
  352. #region DisplayCollectionDifferences
  353. /// <summary>
  354. /// Display the failure information for two collections that did not match.
  355. /// </summary>
  356. /// <param name="writer">The MessageWriter on which to display</param>
  357. /// <param name="expected">The expected collection.</param>
  358. /// <param name="actual">The actual collection</param>
  359. /// <param name="depth">The depth of this failure in a set of nested collections</param>
  360. private void DisplayCollectionDifferences(MessageWriter writer, ICollection expected, ICollection actual, int depth)
  361. {
  362. int failurePoint = comparer.FailurePoints.Count > depth ? (int)comparer.FailurePoints[depth] : -1;
  363. DisplayCollectionTypesAndSizes(writer, expected, actual, depth);
  364. if (failurePoint >= 0)
  365. {
  366. DisplayFailurePoint(writer, expected, actual, failurePoint, depth);
  367. if (failurePoint < expected.Count && failurePoint < actual.Count)
  368. DisplayDifferences(
  369. writer,
  370. GetValueFromCollection(expected, failurePoint),
  371. GetValueFromCollection(actual, failurePoint),
  372. ++depth);
  373. else if (expected.Count < actual.Count)
  374. {
  375. writer.Write( " Extra: " );
  376. writer.WriteCollectionElements( actual, failurePoint, 3 );
  377. }
  378. else
  379. {
  380. writer.Write( " Missing: " );
  381. writer.WriteCollectionElements( expected, failurePoint, 3 );
  382. }
  383. }
  384. }
  385. /// <summary>
  386. /// Displays a single line showing the types and sizes of the expected
  387. /// and actual collections or arrays. If both are identical, the value is
  388. /// only shown once.
  389. /// </summary>
  390. /// <param name="writer">The MessageWriter on which to display</param>
  391. /// <param name="expected">The expected collection or array</param>
  392. /// <param name="actual">The actual collection or array</param>
  393. /// <param name="indent">The indentation level for the message line</param>
  394. private void DisplayCollectionTypesAndSizes(MessageWriter writer, ICollection expected, ICollection actual, int indent)
  395. {
  396. string sExpected = MsgUtils.GetTypeRepresentation(expected);
  397. if (!(expected is Array))
  398. sExpected += string.Format(" with {0} elements", expected.Count);
  399. string sActual = MsgUtils.GetTypeRepresentation(actual);
  400. if (!(actual is Array))
  401. sActual += string.Format(" with {0} elements", actual.Count);
  402. if (sExpected == sActual)
  403. writer.WriteMessageLine(indent, CollectionType_1, sExpected);
  404. else
  405. writer.WriteMessageLine(indent, CollectionType_2, sExpected, sActual);
  406. }
  407. /// <summary>
  408. /// Displays a single line showing the point in the expected and actual
  409. /// arrays at which the comparison failed. If the arrays have different
  410. /// structures or dimensions, both values are shown.
  411. /// </summary>
  412. /// <param name="writer">The MessageWriter on which to display</param>
  413. /// <param name="expected">The expected array</param>
  414. /// <param name="actual">The actual array</param>
  415. /// <param name="failurePoint">Index of the failure point in the underlying collections</param>
  416. /// <param name="indent">The indentation level for the message line</param>
  417. private void DisplayFailurePoint(MessageWriter writer, ICollection expected, ICollection actual, int failurePoint, int indent)
  418. {
  419. Array expectedArray = expected as Array;
  420. Array actualArray = actual as Array;
  421. int expectedRank = expectedArray != null ? expectedArray.Rank : 1;
  422. int actualRank = actualArray != null ? actualArray.Rank : 1;
  423. bool useOneIndex = expectedRank == actualRank;
  424. if (expectedArray != null && actualArray != null)
  425. for (int r = 1; r < expectedRank && useOneIndex; r++)
  426. if (expectedArray.GetLength(r) != actualArray.GetLength(r))
  427. useOneIndex = false;
  428. int[] expectedIndices = MsgUtils.GetArrayIndicesFromCollectionIndex(expected, failurePoint);
  429. if (useOneIndex)
  430. {
  431. writer.WriteMessageLine(indent, ValuesDiffer_1, MsgUtils.GetArrayIndicesAsString(expectedIndices));
  432. }
  433. else
  434. {
  435. int[] actualIndices = MsgUtils.GetArrayIndicesFromCollectionIndex(actual, failurePoint);
  436. writer.WriteMessageLine(indent, ValuesDiffer_2,
  437. MsgUtils.GetArrayIndicesAsString(expectedIndices), MsgUtils.GetArrayIndicesAsString(actualIndices));
  438. }
  439. }
  440. private static object GetValueFromCollection(ICollection collection, int index)
  441. {
  442. Array array = collection as Array;
  443. if (array != null && array.Rank > 1)
  444. return array.GetValue(MsgUtils.GetArrayIndicesFromCollectionIndex(array, index));
  445. if (collection is IList)
  446. return ((IList)collection)[index];
  447. foreach (object obj in collection)
  448. if (--index < 0)
  449. return obj;
  450. return null;
  451. }
  452. #endregion
  453. }
  454. }