1 // ****************************************************************
\r
2 // This is free software licensed under the NUnit license. You
\r
3 // may obtain a copy of the license as well as information regarding
\r
4 // copyright ownership at http://nunit.org/?p=license&r=2.4.
\r
5 // ****************************************************************
\r
10 using System.Collections;
\r
12 namespace NUnit.Framework
\r
15 /// AssertionFailureMessage encapsulates a failure message
\r
16 /// issued as a result of an Assert failure.
\r
18 [Obsolete( "Use MessageWriter for new work" )]
\r
19 public class AssertionFailureMessage : StringWriter
\r
21 #region Static Constants
\r
24 /// Number of characters before a highlighted position before
\r
25 /// clipping will occur. Clipped text is replaced with an
\r
28 static public readonly int PreClipLength = 35;
\r
31 /// Number of characters after a highlighted position before
\r
32 /// clipping will occur. Clipped text is replaced with an
\r
35 static public readonly int PostClipLength = 35;
\r
38 /// Prefix used to start an expected value line.
\r
39 /// Must be same length as actualPrefix.
\r
41 static protected readonly string expectedPrefix = "expected:";
\r
44 /// Prefix used to start an actual value line.
\r
45 /// Must be same length as expectedPrefix.
\r
47 static protected readonly string actualPrefix = " but was:";
\r
49 static private readonly string expectedAndActualFmt = "\t{0} {1}";
\r
50 static private readonly string diffStringLengthsFmt
\r
51 = "\tString lengths differ. Expected length={0}, but was length={1}.";
\r
52 static private readonly string sameStringLengthsFmt
\r
53 = "\tString lengths are both {0}.";
\r
54 static private readonly string diffArrayLengthsFmt
\r
55 = "Array lengths differ. Expected length={0}, but was length={1}.";
\r
56 static private readonly string sameArrayLengthsFmt
\r
57 = "Array lengths are both {0}.";
\r
58 static private readonly string stringsDifferAtIndexFmt
\r
59 = "\tStrings differ at index {0}.";
\r
60 static private readonly string arraysDifferAtIndexFmt
\r
61 = "Arrays differ at index {0}.";
\r
65 #region Constructors
\r
68 /// Construct an AssertionFailureMessage with a message
\r
69 /// and optional arguments.
\r
71 /// <param name="message"></param>
\r
72 /// <param name="args"></param>
\r
73 public AssertionFailureMessage( string message, params object[] args )
\r
75 if ( message != null && message != string.Empty )
\r
77 WriteLine( message, args );
\r
79 WriteLine( message );
\r
83 /// Construct an empty AssertionFailureMessage
\r
85 public AssertionFailureMessage() : this( null, null ) { }
\r
90 /// Add an expected value line to the message containing
\r
91 /// the text provided as an argument.
\r
93 /// <param name="text">Text describing what was expected.</param>
\r
94 public void WriteExpectedLine( string text )
\r
96 WriteLine( string.Format( expectedAndActualFmt, expectedPrefix, text ) );
\r
100 /// Add an actual value line to the message containing
\r
101 /// the text provided as an argument.
\r
103 /// <param name="text">Text describing the actual value.</param>
\r
104 public void WriteActualLine( string text )
\r
106 WriteLine( string.Format( expectedAndActualFmt, actualPrefix, text ) );
\r
110 /// Add an expected value line to the message containing
\r
111 /// a string representation of the object provided.
\r
113 /// <param name="expected">An object representing the expected value</param>
\r
114 public void DisplayExpectedValue( object expected )
\r
116 WriteExpectedLine( FormatObjectForDisplay( expected ) );
\r
120 /// Add an expected value line to the message containing a double
\r
121 /// and the tolerance used in making the comparison.
\r
123 /// <param name="expected">The expected value</param>
\r
124 /// <param name="tolerance">The tolerance specified in the Assert</param>
\r
125 public void DisplayExpectedValue( double expected, double tolerance )
\r
127 WriteExpectedLine( FormatObjectForDisplay( expected ) + " +/- " + tolerance.ToString() );
\r
131 /// Add an actual value line to the message containing
\r
132 /// a string representation of the object provided.
\r
134 /// <param name="actual">An object representing what was actually found</param>
\r
135 public void DisplayActualValue( object actual )
\r
137 WriteActualLine( FormatObjectForDisplay( actual ) );
\r
141 /// Display two lines that communicate the expected value, and the actual value
\r
143 /// <param name="expected">The expected value</param>
\r
144 /// <param name="actual">The actual value found</param>
\r
145 public void DisplayExpectedAndActual( Object expected, Object actual )
\r
147 DisplayExpectedValue( expected );
\r
148 DisplayActualValue( actual );
\r
152 /// Display two lines that communicate the expected value, the actual value and
\r
153 /// the tolerance used in comparing two doubles.
\r
155 /// <param name="expected">The expected value</param>
\r
156 /// <param name="actual">The actual value found</param>
\r
157 /// <param name="tolerance">The tolerance specified in the Assert</param>
\r
158 public void DisplayExpectedAndActual( double expected, double actual, double tolerance )
\r
160 DisplayExpectedValue( expected, tolerance );
\r
161 DisplayActualValue( actual );
\r
165 /// Draws a marker under the expected/actual strings that highlights
\r
166 /// where in the string a mismatch occurred.
\r
168 /// <param name="iPosition">The position of the mismatch</param>
\r
169 public void DisplayPositionMarker( int iPosition )
\r
171 WriteLine( "\t{0}^", new String( '-', expectedPrefix.Length + iPosition + 3 ) );
\r
175 /// Reports whether the string lengths are the same or different, and
\r
176 /// what the string lengths are.
\r
178 /// <param name="sExpected">The expected string</param>
\r
179 /// <param name="sActual">The actual string value</param>
\r
180 protected void BuildStringLengthReport( string sExpected, string sActual )
\r
182 if( sExpected.Length != sActual.Length )
\r
183 WriteLine( diffStringLengthsFmt, sExpected.Length, sActual.Length );
\r
185 WriteLine( sameStringLengthsFmt, sExpected.Length );
\r
189 /// Called to create additional message lines when two objects have been
\r
190 /// found to be unequal. If the inputs are strings, a special message is
\r
191 /// rendered that can help track down where the strings are different,
\r
192 /// based on differences in length, or differences in content.
\r
194 /// If the inputs are not strings, the ToString method of the objects
\r
195 /// is used to show what is different about them.
\r
197 /// <param name="expected">The expected value</param>
\r
198 /// <param name="actual">The actual value</param>
\r
199 /// <param name="caseInsensitive">True if a case-insensitive comparison is being performed</param>
\r
200 public void DisplayDifferences( object expected, object actual, bool caseInsensitive )
\r
202 if( InputsAreStrings( expected, actual ) )
\r
204 DisplayStringDifferences(
\r
211 DisplayExpectedAndActual( expected, actual );
\r
216 /// Called to create additional message lines when two doubles have been
\r
217 /// found to be unequal, within the specified tolerance.
\r
219 public void DisplayDifferencesWithTolerance( double expected, double actual, double tolerance )
\r
221 DisplayExpectedAndActual( expected, actual, tolerance );
\r
225 /// Constructs a message that can be displayed when the content of two
\r
226 /// strings are different, but the string lengths are the same. The
\r
227 /// message will clip the strings to a reasonable length, centered
\r
228 /// around the first position where they are mismatched, and draw
\r
229 /// a line marking the position of the difference to make comparison
\r
232 /// <param name="sExpected">The expected string value</param>
\r
233 /// <param name="sActual">The actual string value</param>
\r
234 /// <param name="caseInsensitive">True if a case-insensitive comparison is being performed</param>
\r
235 protected void DisplayStringDifferences( string sExpected, string sActual, bool caseInsensitive )
\r
238 // If they mismatch at a specified position, report the
\r
241 int iPosition = caseInsensitive
\r
242 ? FindMismatchPosition( sExpected.ToLower(), sActual.ToLower(), 0 )
\r
243 : FindMismatchPosition( sExpected, sActual, 0 );
\r
245 // If the lengths differ, but they match up to the length,
\r
246 // show the difference just past the length of the shorter
\r
249 if( iPosition == -1 )
\r
250 iPosition = Math.Min( sExpected.Length, sActual.Length );
\r
252 BuildStringLengthReport( sExpected, sActual );
\r
254 WriteLine( stringsDifferAtIndexFmt, iPosition );
\r
257 // Clips the strings, then turns any hidden whitespace into visible
\r
260 string sClippedExpected = ConvertWhitespace(ClipAroundPosition( sExpected, iPosition ));
\r
261 string sClippedActual = ConvertWhitespace(ClipAroundPosition( sActual, iPosition ));
\r
263 DisplayExpectedAndActual(
\r
267 // Add a line showing where they differ. If the string lengths are
\r
268 // different, they start differing just past the length of the
\r
270 DisplayPositionMarker( caseInsensitive
\r
271 ? FindMismatchPosition( sClippedExpected.ToLower(), sClippedActual.ToLower(), 0 )
\r
272 : FindMismatchPosition( sClippedExpected, sClippedActual, 0 ) );
\r
276 /// Display a standard message showing the differences found between
\r
277 /// two arrays that were expected to be equal.
\r
279 /// <param name="expected">The expected array value</param>
\r
280 /// <param name="actual">The actual array value</param>
\r
281 /// <param name="index">The index at which a difference was found</param>
\r
282 public void DisplayArrayDifferences( Array expected, Array actual, int index )
\r
284 if( expected.Length != actual.Length )
\r
285 WriteLine( diffArrayLengthsFmt, expected.Length, actual.Length );
\r
287 WriteLine( sameArrayLengthsFmt, expected.Length );
\r
289 WriteLine( arraysDifferAtIndexFmt, index );
\r
291 if ( index < expected.Length && index < actual.Length )
\r
293 DisplayDifferences( GetValueFromCollection(expected, index ), GetValueFromCollection(actual, index), false );
\r
295 else if( expected.Length < actual.Length )
\r
296 DisplayListElements( " extra:", actual, index, 3 );
\r
298 DisplayListElements( " missing:", expected, index, 3 );
\r
302 /// Display a standard message showing the differences found between
\r
303 /// two collections that were expected to be equal.
\r
305 /// <param name="expected">The expected collection value</param>
\r
306 /// <param name="actual">The actual collection value</param>
\r
307 /// <param name="index">The index at which a difference was found</param>
\r
308 // NOTE: This is a temporary method for use until the code from NUnitLite
\r
309 // is integrated into NUnit.
\r
310 public void DisplayCollectionDifferences( ICollection expected, ICollection actual, int index )
\r
312 if( expected.Count != actual.Count )
\r
313 WriteLine( diffArrayLengthsFmt, expected.Count, actual.Count );
\r
315 WriteLine( sameArrayLengthsFmt, expected.Count );
\r
317 WriteLine( arraysDifferAtIndexFmt, index );
\r
319 if ( index < expected.Count && index < actual.Count )
\r
321 DisplayDifferences( GetValueFromCollection(expected, index ), GetValueFromCollection(actual, index), false );
\r
323 // else if( expected.Count < actual.Count )
\r
324 // DisplayListElements( " extra:", actual, index, 3 );
\r
326 // DisplayListElements( " missing:", expected, index, 3 );
\r
329 private static object GetValueFromCollection(ICollection collection, int index)
\r
331 Array array = collection as Array;
\r
333 if (array != null && array.Rank > 1)
\r
334 return array.GetValue(GetArrayIndicesFromCollectionIndex(array, index));
\r
336 if (collection is IList)
\r
337 return ((IList)collection)[index];
\r
339 foreach (object obj in collection)
\r
347 /// Get an array of indices representing the point in a collection or
\r
348 /// array corresponding to a single int index into the collection.
\r
350 /// <param name="collection">The collection to which the indices apply</param>
\r
351 /// <param name="index">Index in the collection</param>
\r
352 /// <returns>Array of indices</returns>
\r
353 private static int[] GetArrayIndicesFromCollectionIndex(ICollection collection, int index)
\r
355 Array array = collection as Array;
\r
356 int rank = array == null ? 1 : array.Rank;
\r
357 int[] result = new int[rank];
\r
359 for (int r = array.Rank; --r > 0; )
\r
361 int l = array.GetLength(r);
\r
362 result[r] = index % l;
\r
371 /// Displays elements from a list on a line
\r
373 /// <param name="label">Text to prefix the line with</param>
\r
374 /// <param name="list">The list of items to display</param>
\r
375 /// <param name="index">The index in the list of the first element to display</param>
\r
376 /// <param name="max">The maximum number of elements to display</param>
\r
377 public void DisplayListElements( string label, IList list, int index, int max )
\r
379 Write( "{0}<", label );
\r
381 if ( list == null )
\r
383 else if ( list.Count == 0 )
\r
387 for( int i = 0; i < max && index < list.Count; i++ )
\r
389 Write( FormatObjectForDisplay( list[index++] ) );
\r
391 if ( index < list.Count )
\r
395 if ( index < list.Count )
\r
402 #region Static Methods
\r
405 /// Formats an object for display in a message line
\r
407 /// <param name="obj">The object to be displayed</param>
\r
408 /// <returns></returns>
\r
409 static public string FormatObjectForDisplay( object obj )
\r
411 if ( obj == null )
\r
413 else if ( obj is string )
\r
414 return string.Format( "<\"{0}\">", obj );
\r
415 else if ( obj is double )
\r
416 return string.Format( "<{0}>", ((double)obj).ToString( "G17" ) );
\r
417 else if ( obj is float )
\r
418 return string.Format( "<{0}>", ((float)obj).ToString( "G9" ) );
\r
420 return string.Format( "<{0}>", obj );
\r
424 /// Tests two objects to determine if they are strings.
\r
426 /// <param name="expected"></param>
\r
427 /// <param name="actual"></param>
\r
428 /// <returns></returns>
\r
429 static protected bool InputsAreStrings( Object expected, Object actual )
\r
431 return expected != null && actual != null &&
\r
432 expected is string && actual is string;
\r
436 /// Renders up to M characters before, and up to N characters after
\r
437 /// the specified index position. If leading or trailing text is
\r
438 /// clipped, and elipses "..." is added where the missing text would
\r
441 /// Clips strings to limit previous or post newline characters,
\r
442 /// since these mess up the comparison
\r
444 /// <param name="sString"></param>
\r
445 /// <param name="iPosition"></param>
\r
446 /// <returns></returns>
\r
447 static protected string ClipAroundPosition( string sString, int iPosition )
\r
449 if( sString == null || sString.Length == 0 )
\r
452 bool preClip = iPosition > PreClipLength;
\r
453 bool postClip = iPosition + PostClipLength < sString.Length;
\r
455 int start = preClip
\r
456 ? iPosition - PreClipLength : 0;
\r
457 int length = postClip
\r
458 ? iPosition + PostClipLength - start : sString.Length - start;
\r
460 if ( start + length > iPosition + PostClipLength )
\r
461 length = iPosition + PostClipLength - start;
\r
463 StringBuilder sb = new StringBuilder();
\r
464 if ( preClip ) sb.Append("...");
\r
465 sb.Append( sString.Substring( start, length ) );
\r
466 if ( postClip ) sb.Append("...");
\r
468 return sb.ToString();
\r
472 /// Shows the position two strings start to differ. Comparison
\r
473 /// starts at the start index.
\r
475 /// <param name="sExpected"></param>
\r
476 /// <param name="sActual"></param>
\r
477 /// <param name="iStart"></param>
\r
478 /// <returns>-1 if no mismatch found, or the index where mismatch found</returns>
\r
479 static private int FindMismatchPosition( string sExpected, string sActual, int iStart )
\r
481 int iLength = Math.Min( sExpected.Length, sActual.Length );
\r
482 for( int i=iStart; i<iLength; i++ )
\r
485 // If they mismatch at a specified position, report the
\r
488 if( sExpected[i] != sActual[i] )
\r
494 // Strings have same content up to the length of the shorter string.
\r
495 // Mismatch occurs because string lengths are different, so show
\r
496 // that they start differing where the shortest string ends
\r
498 if( sExpected.Length != sActual.Length )
\r
506 Assert.IsTrue( sExpected.Equals( sActual ) );
\r
511 /// Turns CR, LF, or TAB into visual indicator to preserve visual marker
\r
512 /// position. This is done by replacing the '\r' into '\\' and 'r'
\r
513 /// characters, and the '\n' into '\\' and 'n' characters, and '\t' into
\r
514 /// '\\' and 't' characters.
\r
516 /// Thus the single character becomes two characters for display.
\r
518 /// <param name="sInput"></param>
\r
519 /// <returns></returns>
\r
520 static protected string ConvertWhitespace( string sInput )
\r
522 if( null != sInput )
\r
524 sInput = sInput.Replace( "\\", "\\\\" );
\r
525 sInput = sInput.Replace( "\r", "\\r" );
\r
526 sInput = sInput.Replace( "\n", "\\n" );
\r
527 sInput = sInput.Replace( "\t", "\\t" );
\r