PageRenderTime 44ms CodeModel.GetById 18ms app.highlight 18ms RepoModel.GetById 2ms app.codeStats 0ms

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

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