1 // ****************************************************************
2 // This is free software licensed under the NUnit license. You
3 // may obtain a copy of the license as well as information regarding
4 // copyright ownership at http://nunit.org/?p=license&r=2.4.
5 // ****************************************************************
6 
7 using System;
8 using System.Text;
9 using System.IO;
10 using System.Collections;
11 
12 namespace NUnit.Framework
13 {
14 	/// <summary>
15 	/// AssertionFailureMessage encapsulates a failure message
16 	/// issued as a result of an Assert failure.
17 	/// </summary>
18 	[Obsolete( "Use MessageWriter for new work" )]
19 	public class AssertionFailureMessage : StringWriter
20 	{
21 		#region Static Constants
22 
23 		/// <summary>
24 		/// Number of characters before a highlighted position before
25 		/// clipping will occur.  Clipped text is replaced with an
26 		/// elipsis "..."
27 		/// </summary>
28 		static public readonly int PreClipLength = 35;
29 
30 		/// <summary>
31 		/// Number of characters after a highlighted position before
32 		/// clipping will occur.  Clipped text is replaced with an
33 		/// elipsis "..."
34 		/// </summary>
35 		static public readonly int PostClipLength = 35;
36 
37 		/// <summary>
38 		/// Prefix used to start an expected value line.
39 		/// Must be same length as actualPrefix.
40 		/// </summary>
41 		static protected readonly string expectedPrefix = "expected:";
42 
43 		/// <summary>
44 		/// Prefix used to start an actual value line.
45 		/// Must be same length as expectedPrefix.
46 		/// </summary>
47 		static protected readonly string actualPrefix   = " but was:";
48 
49 		static private readonly string expectedAndActualFmt = "\t{0} {1}";
50 		static private readonly string diffStringLengthsFmt
51 			= "\tString lengths differ.  Expected length={0}, but was length={1}.";
52 		static private readonly string sameStringLengthsFmt
53 			= "\tString lengths are both {0}.";
54 		static private readonly string diffArrayLengthsFmt
55 			= "Array lengths differ.  Expected length={0}, but was length={1}.";
56 		static private readonly string sameArrayLengthsFmt
57 			= "Array lengths are both {0}.";
58 		static private readonly string stringsDifferAtIndexFmt
59 			= "\tStrings differ at index {0}.";
60 		static private readonly string arraysDifferAtIndexFmt
61 			= "Arrays differ at index {0}.";
62 
63 		#endregion
64 
65 		#region Constructors
66 
67 		/// <summary>
68 		/// Construct an AssertionFailureMessage with a message
69 		/// and optional arguments.
70 		/// </summary>
71 		/// <param name="message"></param>
72 		/// <param name="args"></param>
AssertionFailureMessage( string message, params object[] args )73 		public AssertionFailureMessage( string message, params object[] args )
74 		{
75 			if ( message != null && message != string.Empty )
76 				if ( args != null )
77 					WriteLine( message, args );
78 				else
79 					WriteLine( message );
80 		}
81 
82 		/// <summary>
83 		/// Construct an empty AssertionFailureMessage
84 		/// </summary>
AssertionFailureMessage()85 		public AssertionFailureMessage() : this( null, null ) { }
86 
87 		#endregion
88 
89 		/// <summary>
90 		/// Add an expected value line to the message containing
91 		/// the text provided as an argument.
92 		/// </summary>
93 		/// <param name="text">Text describing what was expected.</param>
WriteExpectedLine( string text )94 		public void WriteExpectedLine( string text )
95 		{
96 			WriteLine( string.Format( expectedAndActualFmt, expectedPrefix, text ) );
97 		}
98 
99 		/// <summary>
100 		/// Add an actual value line to the message containing
101 		/// the text provided as an argument.
102 		/// </summary>
103 		/// <param name="text">Text describing the actual value.</param>
WriteActualLine( string text )104 		public void WriteActualLine( string text )
105 		{
106 			WriteLine( string.Format( expectedAndActualFmt, actualPrefix, text ) );
107 		}
108 
109 		/// <summary>
110 		/// Add an expected value line to the message containing
111 		/// a string representation of the object provided.
112 		/// </summary>
113 		/// <param name="expected">An object representing the expected value</param>
DisplayExpectedValue( object expected )114 		public void DisplayExpectedValue( object expected )
115 		{
116 			WriteExpectedLine( FormatObjectForDisplay( expected ) );
117 		}
118 
119 		/// <summary>
120 		/// Add an expected value line to the message containing a double
121 		/// and the tolerance used in making the comparison.
122 		/// </summary>
123 		/// <param name="expected">The expected value</param>
124 		/// <param name="tolerance">The tolerance specified in the Assert</param>
DisplayExpectedValue( double expected, double tolerance )125 		public void DisplayExpectedValue( double expected, double tolerance )
126 		{
127 			WriteExpectedLine( FormatObjectForDisplay( expected ) + " +/- " + tolerance.ToString() );
128 		}
129 
130 		/// <summary>
131 		/// Add an actual value line to the message containing
132 		/// a string representation of the object provided.
133 		/// </summary>
134 		/// <param name="actual">An object representing what was actually found</param>
DisplayActualValue( object actual )135 		public void DisplayActualValue( object actual )
136 		{
137 			WriteActualLine( FormatObjectForDisplay( actual ) );
138 		}
139 
140 		/// <summary>
141 		/// Display two lines that communicate the expected value, and the actual value
142 		/// </summary>
143 		/// <param name="expected">The expected value</param>
144 		/// <param name="actual">The actual value found</param>
DisplayExpectedAndActual( Object expected, Object actual )145 		public void DisplayExpectedAndActual( Object expected, Object actual )
146 		{
147 			DisplayExpectedValue( expected );
148 			DisplayActualValue( actual );
149 		}
150 
151 		/// <summary>
152 		/// Display two lines that communicate the expected value, the actual value and
153 		/// the tolerance used in comparing two doubles.
154 		/// </summary>
155 		/// <param name="expected">The expected value</param>
156 		/// <param name="actual">The actual value found</param>
157 		/// <param name="tolerance">The tolerance specified in the Assert</param>
DisplayExpectedAndActual( double expected, double actual, double tolerance )158 		public void DisplayExpectedAndActual( double expected, double actual, double tolerance )
159 		{
160 			DisplayExpectedValue( expected, tolerance );
161 			DisplayActualValue( actual );
162 		}
163 
164 		/// <summary>
165 		/// Draws a marker under the expected/actual strings that highlights
166 		/// where in the string a mismatch occurred.
167 		/// </summary>
168 		/// <param name="iPosition">The position of the mismatch</param>
DisplayPositionMarker( int iPosition )169 		public void DisplayPositionMarker( int iPosition )
170 		{
171 			WriteLine( "\t{0}^", new String( '-', expectedPrefix.Length + iPosition + 3 ) );
172 		}
173 
174 		/// <summary>
175 		/// Reports whether the string lengths are the same or different, and
176 		/// what the string lengths are.
177 		/// </summary>
178 		/// <param name="sExpected">The expected string</param>
179 		/// <param name="sActual">The actual string value</param>
BuildStringLengthReport( string sExpected, string sActual )180 		protected void BuildStringLengthReport( string sExpected, string sActual )
181 		{
182 			if( sExpected.Length != sActual.Length )
183 				WriteLine( diffStringLengthsFmt, sExpected.Length, sActual.Length );
184 			else
185 				WriteLine( sameStringLengthsFmt, sExpected.Length );
186 		}
187 
188 		/// <summary>
189 		/// Called to create additional message lines when two objects have been
190 		/// found to be unequal.  If the inputs are strings, a special message is
191 		/// rendered that can help track down where the strings are different,
192 		/// based on differences in length, or differences in content.
193 		///
194 		/// If the inputs are not strings, the ToString method of the objects
195 		/// is used to show what is different about them.
196 		/// </summary>
197 		/// <param name="expected">The expected value</param>
198 		/// <param name="actual">The actual value</param>
199 		/// <param name="caseInsensitive">True if a case-insensitive comparison is being performed</param>
DisplayDifferences( object expected, object actual, bool caseInsensitive )200 		public void DisplayDifferences( object expected, object actual, bool caseInsensitive )
201 		{
202 			if( InputsAreStrings( expected, actual ) )
203 			{
204 				DisplayStringDifferences(
205 					(string)expected,
206 					(string)actual,
207 					caseInsensitive );
208 			}
209 			else
210 			{
211 				DisplayExpectedAndActual( expected, actual );
212 			}
213 		}
214 
215 		/// <summary>
216 		/// Called to create additional message lines when two doubles have been
217 		/// found to be unequal, within the specified tolerance.
218 		/// </summary>
DisplayDifferencesWithTolerance( double expected, double actual, double tolerance )219 		public void DisplayDifferencesWithTolerance( double expected, double actual, double tolerance )
220 		{
221 			DisplayExpectedAndActual( expected, actual, tolerance );
222 		}
223 
224 		/// <summary>
225 		/// Constructs a message that can be displayed when the content of two
226 		/// strings are different, but the string lengths are the same.  The
227 		/// message will clip the strings to a reasonable length, centered
228 		/// around the first position where they are mismatched, and draw
229 		/// a line marking the position of the difference to make comparison
230 		/// quicker.
231 		/// </summary>
232 		/// <param name="sExpected">The expected string value</param>
233 		/// <param name="sActual">The actual string value</param>
234 		/// <param name="caseInsensitive">True if a case-insensitive comparison is being performed</param>
DisplayStringDifferences( string sExpected, string sActual, bool caseInsensitive )235 		protected void DisplayStringDifferences( string sExpected, string sActual, bool caseInsensitive )
236 		{
237 			//
238 			// If they mismatch at a specified position, report the
239 			// difference.
240 			//
241 			int iPosition = caseInsensitive
242 				? FindMismatchPosition( sExpected.ToLower(), sActual.ToLower(), 0 )
243 				: FindMismatchPosition( sExpected, sActual, 0 );
244 			//
245 			// If the lengths differ, but they match up to the length,
246 			// show the difference just past the length of the shorter
247 			// string
248 			//
249 			if( iPosition == -1 )
250 				iPosition = Math.Min( sExpected.Length, sActual.Length );
251 
252 			BuildStringLengthReport( sExpected, sActual );
253 
254 			WriteLine( stringsDifferAtIndexFmt, iPosition );
255 
256 			//
257 			// Clips the strings, then turns any hidden whitespace into visible
258 			// characters
259 			//
260 			string sClippedExpected = ConvertWhitespace(ClipAroundPosition( sExpected, iPosition ));
261 			string sClippedActual   = ConvertWhitespace(ClipAroundPosition( sActual,   iPosition ));
262 
263 			DisplayExpectedAndActual(
264 				sClippedExpected,
265 				sClippedActual );
266 
267 			// Add a line showing where they differ.  If the string lengths are
268 			// different, they start differing just past the length of the
269 			// shorter string
270 			DisplayPositionMarker( caseInsensitive
271 				? FindMismatchPosition( sClippedExpected.ToLower(), sClippedActual.ToLower(), 0 )
272 				: FindMismatchPosition( sClippedExpected, sClippedActual, 0 ) );
273 		}
274 
275 		/// <summary>
276 		/// Display a standard message showing the differences found between
277 		/// two arrays that were expected to be equal.
278 		/// </summary>
279 		/// <param name="expected">The expected array value</param>
280 		/// <param name="actual">The actual array value</param>
281 		/// <param name="index">The index at which a difference was found</param>
DisplayArrayDifferences( Array expected, Array actual, int index )282 		public void DisplayArrayDifferences( Array expected, Array actual, int index )
283 		{
284 			if( expected.Length != actual.Length )
285 				WriteLine( diffArrayLengthsFmt, expected.Length, actual.Length );
286 			else
287 				WriteLine( sameArrayLengthsFmt, expected.Length );
288 
289 			WriteLine( arraysDifferAtIndexFmt, index );
290 
291 			if ( index < expected.Length && index < actual.Length )
292 			{
293 				DisplayDifferences( GetValueFromCollection(expected, index ), GetValueFromCollection(actual, index), false );
294 			}
295 			else if( expected.Length < actual.Length )
296 				DisplayListElements( "   extra:", actual, index, 3 );
297 			else
298 				DisplayListElements( " missing:", expected, index, 3 );
299 		}
300 
301 		/// <summary>
302 		/// Display a standard message showing the differences found between
303 		/// two collections that were expected to be equal.
304 		/// </summary>
305 		/// <param name="expected">The expected collection value</param>
306 		/// <param name="actual">The actual collection value</param>
307 		/// <param name="index">The index at which a difference was found</param>
308 		// NOTE: This is a temporary method for use until the code from NUnitLite
309 		// is integrated into NUnit.
DisplayCollectionDifferences( ICollection expected, ICollection actual, int index )310 		public void DisplayCollectionDifferences( ICollection expected, ICollection actual, int index )
311 		{
312 			if( expected.Count != actual.Count )
313 				WriteLine( diffArrayLengthsFmt, expected.Count, actual.Count );
314 			else
315 				WriteLine( sameArrayLengthsFmt, expected.Count );
316 
317 			WriteLine( arraysDifferAtIndexFmt, index );
318 
319 			if ( index < expected.Count && index < actual.Count )
320 			{
321 				DisplayDifferences( GetValueFromCollection(expected, index ), GetValueFromCollection(actual, index), false );
322 			}
323 //			else if( expected.Count < actual.Count )
324 //				DisplayListElements( "   extra:", actual, index, 3 );
325 //			else
326 //				DisplayListElements( " missing:", expected, index, 3 );
327 		}
328 
GetValueFromCollection(ICollection collection, int index)329 		private static object GetValueFromCollection(ICollection collection, int index)
330 		{
331 			Array array = collection as Array;
332 
333 			if (array != null && array.Rank > 1)
334 				return array.GetValue(GetArrayIndicesFromCollectionIndex(array, index));
335 
336 			if (collection is IList)
337 				return ((IList)collection)[index];
338 
339 			foreach (object obj in collection)
340 				if (--index < 0)
341 					return obj;
342 
343 			return null;
344 		}
345 
346 		/// <summary>
347 		/// Get an array of indices representing the point in a collection or
348 		/// array corresponding to a single int index into the collection.
349 		/// </summary>
350 		/// <param name="collection">The collection to which the indices apply</param>
351 		/// <param name="index">Index in the collection</param>
352 		/// <returns>Array of indices</returns>
GetArrayIndicesFromCollectionIndex(ICollection collection, int index)353 		private static int[] GetArrayIndicesFromCollectionIndex(ICollection collection, int index)
354 		{
355 			Array array = collection as Array;
356 			int rank = array == null ? 1 : array.Rank;
357 			int[] result = new int[rank];
358 
359 			for (int r = array.Rank; --r > 0; )
360 			{
361 				int l = array.GetLength(r);
362 				result[r] = index % l;
363 				index /= l;
364 			}
365 
366 			result[0] = index;
367 			return result;
368 		}
369 
370 		/// <summary>
371 		/// Displays elements from a list on a line
372 		/// </summary>
373 		/// <param name="label">Text to prefix the line with</param>
374 		/// <param name="list">The list of items to display</param>
375 		/// <param name="index">The index in the list of the first element to display</param>
376 		/// <param name="max">The maximum number of elements to display</param>
DisplayListElements( string label, IList list, int index, int max )377 		public void DisplayListElements( string label, IList list, int index, int max )
378 		{
379 			Write( "{0}<", label );
380 
381 			if ( list == null )
382 				Write( "null" );
383 			else if ( list.Count == 0 )
384 				Write( "empty" );
385 			else
386 			{
387 				for( int i = 0; i < max && index < list.Count; i++ )
388 				{
389 					Write( FormatObjectForDisplay( list[index++] ) );
390 
391 					if ( index < list.Count )
392 						Write( "," );
393 				}
394 
395 				if ( index < list.Count )
396 					Write( "..." );
397 			}
398 
399 			WriteLine( ">" );
400 		}
401 
402 		#region Static Methods
403 
404 		/// <summary>
405 		/// Formats an object for display in a message line
406 		/// </summary>
407 		/// <param name="obj">The object to be displayed</param>
408 		/// <returns></returns>
FormatObjectForDisplay( object obj )409 		static public string FormatObjectForDisplay( object  obj )
410 		{
411 			if ( obj == null )
412 				return "<(null)>";
413 			else if ( obj is string )
414 				return string.Format( "<\"{0}\">", obj );
415 			else if ( obj is double )
416 				return string.Format( "<{0}>", ((double)obj).ToString( "G17" ) );
417 			else if ( obj is float )
418 				return string.Format( "<{0}>", ((float)obj).ToString( "G9" ) );
419 			else
420 				return string.Format( "<{0}>", obj );
421 		}
422 
423 		/// <summary>
424 		/// Tests two objects to determine if they are strings.
425 		/// </summary>
426 		/// <param name="expected"></param>
427 		/// <param name="actual"></param>
428 		/// <returns></returns>
InputsAreStrings( Object expected, Object actual )429 		static protected bool InputsAreStrings( Object expected, Object actual )
430 		{
431 			return expected != null && actual != null &&
432 				expected is string && actual is string;
433 		}
434 
435 		/// <summary>
436 		/// Renders up to M characters before, and up to N characters after
437 		/// the specified index position.  If leading or trailing text is
438 		/// clipped, and elipses "..." is added where the missing text would
439 		/// be.
440 		///
441 		/// Clips strings to limit previous or post newline characters,
442 		/// since these mess up the comparison
443 		/// </summary>
444 		/// <param name="sString"></param>
445 		/// <param name="iPosition"></param>
446 		/// <returns></returns>
ClipAroundPosition( string sString, int iPosition )447 		static protected string ClipAroundPosition( string sString, int iPosition )
448 		{
449 			if( sString == null || sString.Length == 0 )
450 				return "";
451 
452 			bool preClip = iPosition > PreClipLength;
453 			bool postClip = iPosition + PostClipLength < sString.Length;
454 
455 			int start = preClip
456 				? iPosition - PreClipLength : 0;
457 			int length = postClip
458 				? iPosition + PostClipLength - start : sString.Length - start;
459 
460 			if ( start + length > iPosition + PostClipLength )
461 				length = iPosition + PostClipLength - start;
462 
463 			StringBuilder sb = new StringBuilder();
464 			if ( preClip ) sb.Append("...");
465 			sb.Append( sString.Substring( start, length ) );
466 			if ( postClip ) sb.Append("...");
467 
468 			return sb.ToString();
469 		}
470 
471 		/// <summary>
472 		/// Shows the position two strings start to differ.  Comparison
473 		/// starts at the start index.
474 		/// </summary>
475 		/// <param name="sExpected"></param>
476 		/// <param name="sActual"></param>
477 		/// <param name="iStart"></param>
478 		/// <returns>-1 if no mismatch found, or the index where mismatch found</returns>
FindMismatchPosition( string sExpected, string sActual, int iStart )479 		static private int FindMismatchPosition( string sExpected, string sActual, int iStart )
480 		{
481 			int iLength = Math.Min( sExpected.Length, sActual.Length );
482 			for( int i=iStart; i<iLength; i++ )
483 			{
484 				//
485 				// If they mismatch at a specified position, report the
486 				// difference.
487 				//
488 				if( sExpected[i] != sActual[i] )
489 				{
490 					return i;
491 				}
492 			}
493 			//
494 			// Strings have same content up to the length of the shorter string.
495 			// Mismatch occurs because string lengths are different, so show
496 			// that they start differing where the shortest string ends
497 			//
498 			if( sExpected.Length != sActual.Length )
499 			{
500 				return iLength;
501 			}
502 
503 			//
504 			// Same strings
505 			//
506 			Assert.IsTrue( sExpected.Equals( sActual ) );
507 			return -1;
508 		}
509 
510 		/// <summary>
511 		/// Turns CR, LF, or TAB into visual indicator to preserve visual marker
512 		/// position.   This is done by replacing the '\r' into '\\' and 'r'
513 		/// characters, and the '\n' into '\\' and 'n' characters, and '\t' into
514 		/// '\\' and 't' characters.
515 		///
516 		/// Thus the single character becomes two characters for display.
517 		/// </summary>
518 		/// <param name="sInput"></param>
519 		/// <returns></returns>
ConvertWhitespace( string sInput )520 		static protected string ConvertWhitespace( string sInput )
521 		{
522 			if( null != sInput )
523 			{
524 				sInput = sInput.Replace( "\\", "\\\\" );
525 				sInput = sInput.Replace( "\r", "\\r" );
526 				sInput = sInput.Replace( "\n", "\\n" );
527 				sInput = sInput.Replace( "\t", "\\t" );
528 			}
529 			return sInput;
530 		}
531 		#endregion
532 	}
533 }
534