**** Merged from MCS ****
[mono-project.git] / mcs / nunit20 / framework / AssertionFailureMessage.cs
blob312de6acf90575814580a5237dd71a189dd3e667
1 #region Copyright (c) 2002-2003, James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole, Philip A. Craig, Douglas de la Torre
2 /************************************************************************************
4 ' Copyright 2002-2003 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole
5 ' Copyright 2000-2002 Philip A. Craig
6 ' Copyright 2001 Douglas de la Torre
8 ' This software is provided 'as-is', without any express or implied warranty. In no
9 ' event will the authors be held liable for any damages arising from the use of this
10 ' software.
12 ' Permission is granted to anyone to use this software for any purpose, including
13 ' commercial applications, and to alter it and redistribute it freely, subject to the
14 ' following restrictions:
16 ' 1. The origin of this software must not be misrepresented; you must not claim that
17 ' you wrote the original software. If you use this software in a product, an
18 ' acknowledgment (see the following) in the product documentation is required.
20 ' Portions Copyright 2002 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov
21 ' Copyright 2000-2002 Philip A. Craig, or Copyright 2001 Douglas de la Torre
23 ' 2. Altered source versions must be plainly marked as such, and must not be
24 ' misrepresented as being the original software.
26 ' 3. This notice may not be removed or altered from any source distribution.
28 '***********************************************************************************/
29 #endregion
31 using System;
32 using System.Text;
34 namespace NUnit.Framework
36 /// <summary>
37 /// Summary description for AssertionFailureMessage.
38 /// </summary>
39 public class AssertionFailureMessage
41 /// <summary>
42 /// Protected constructor, used since this class is only used via
43 /// static methods
44 /// </summary>
45 protected AssertionFailureMessage()
48 /// <summary>
49 /// Number of characters before a highlighted position before
50 /// clipping will occur. Clipped text is replaced with an
51 /// elipses "..."
52 /// </summary>
53 static protected int PreClipLength
55 get
57 return 35;
61 /// <summary>
62 /// Number of characters after a highlighted position before
63 /// clipping will occur. Clipped text is replaced with an
64 /// elipses "..."
65 /// </summary>
66 static protected int PostClipLength
68 get
70 return 35;
74 /// <summary>
75 /// Called to test if the position will cause clipping
76 /// to occur in the early part of a string.
77 /// </summary>
78 /// <param name="iPosition"></param>
79 /// <returns></returns>
80 static private bool IsPreClipped( int iPosition )
82 if( iPosition > PreClipLength )
84 return true;
86 return false;
89 /// <summary>
90 /// Called to test if the position will cause clipping
91 /// to occur in the later part of a string past the
92 /// specified position.
93 /// </summary>
94 /// <param name="sString"></param>
95 /// <param name="iPosition"></param>
96 /// <returns></returns>
97 static private bool IsPostClipped( string sString, int iPosition )
99 if( sString.Length - iPosition > PostClipLength )
101 return true;
103 return false;
106 /// <summary>
107 /// Property called to insert newline characters into a string
108 /// </summary>
109 static private string NewLine
113 return "\r\n\t";
117 /// <summary>
118 /// Renders up to M characters before, and up to N characters after
119 /// the specified index position. If leading or trailing text is
120 /// clipped, and elipses "..." is added where the missing text would
121 /// be.
122 ///
123 /// Clips strings to limit previous or post newline characters,
124 /// since these mess up the comparison
125 /// </summary>
126 /// <param name="sString"></param>
127 /// <param name="iPosition"></param>
128 /// <returns></returns>
129 static protected string ClipAroundPosition( string sString, int iPosition )
131 if( null == sString || 0 == sString.Length )
133 return "";
136 return BuildBefore( sString, iPosition ) + BuildAfter( sString, iPosition );
139 /// <summary>
140 /// Clips the string before the specified position, and appends
141 /// ellipses (...) to show that clipping has occurred
142 /// </summary>
143 /// <param name="sString"></param>
144 /// <param name="iPosition"></param>
145 /// <returns></returns>
146 static protected string PreClip( string sString, int iPosition )
148 return "..." + sString.Substring( iPosition - PreClipLength, PreClipLength );
151 /// <summary>
152 /// Clips the string after the specified position, and appends
153 /// ellipses (...) to show that clipping has occurred
154 /// </summary>
155 /// <param name="sString"></param>
156 /// <param name="iPosition"></param>
157 /// <returns></returns>
158 static protected string PostClip( string sString, int iPosition )
160 return sString.Substring( iPosition, PostClipLength ) + "...";
163 /// <summary>
164 /// Builds the first half of a string, limiting the number of
165 /// characters before the position, and removing newline
166 /// characters. If the leading string is truncated, the
167 /// ellipses (...) characters are appened.
168 /// </summary>
169 /// <param name="sString"></param>
170 /// <param name="iPosition"></param>
171 /// <returns></returns>
172 static private string BuildBefore( string sString, int iPosition )
174 if( IsPreClipped(iPosition) )
176 return PreClip( sString, iPosition );
178 return sString.Substring( 0, iPosition );
181 /// <summary>
182 /// Builds the last half of a string, limiting the number of
183 /// characters after the position, and removing newline
184 /// characters. If the string is truncated, the
185 /// ellipses (...) characters are appened.
186 /// </summary>
187 /// <param name="sString"></param>
188 /// <param name="iPosition"></param>
189 /// <returns></returns>
190 static private string BuildAfter( string sString, int iPosition )
192 if( IsPostClipped(sString, iPosition) )
194 return PostClip( sString, iPosition );
196 return sString.Substring( iPosition );
199 /// <summary>
200 /// Text that is rendered for the expected value
201 /// </summary>
202 /// <returns></returns>
203 static protected string ExpectedText()
205 return "expected:<";
208 /// <summary>
209 /// Text rendered for the actual value. This text should
210 /// be the same length as the Expected text, so leading
211 /// spaces should pad this string to ensure they match.
212 /// </summary>
213 /// <returns></returns>
214 static protected string ButWasText()
216 return " but was:<";
219 /// <summary>
220 /// Raw line that communicates the expected value, and the actual value
221 /// </summary>
222 /// <param name="sbOutput"></param>
223 /// <param name="expected"></param>
224 /// <param name="actual"></param>
225 static protected void AppendExpectedAndActual( StringBuilder sbOutput, Object expected, Object actual )
227 sbOutput.Append( NewLine );
228 sbOutput.Append( ExpectedText() );
229 sbOutput.Append( DisplayString( expected ) );
230 sbOutput.Append( ">" );
231 sbOutput.Append( NewLine );
232 sbOutput.Append( ButWasText() );
233 sbOutput.Append( DisplayString( actual ) );
234 sbOutput.Append( ">" );
237 /// <summary>
238 /// Display an object as a string
239 /// </summary>
240 /// <param name="obj"></param>
241 /// <returns></returns>
242 static protected string DisplayString( object obj )
244 if ( obj == null )
245 return "(null)";
246 else if ( obj is string )
247 return Quoted( (string)obj );
248 else
249 return obj.ToString();
252 /// <summary>
253 /// Quote a string
254 /// </summary>
255 /// <param name="text"></param>
256 /// <returns></returns>
257 static protected string Quoted( string text )
259 return string.Format( "\"{0}\"", text );
262 /// <summary>
263 /// Draws a marker under the expected/actual strings that highlights
264 /// where in the string a mismatch occurred.
265 /// </summary>
266 /// <param name="sbOutput"></param>
267 /// <param name="iPosition"></param>
268 static protected void AppendPositionMarker( StringBuilder sbOutput, int iPosition )
270 sbOutput.Append( new String( '-', ButWasText().Length + 1 ) );
271 if( iPosition > 0 )
273 sbOutput.Append( new string( '-', iPosition ) );
275 sbOutput.Append( "^" );
278 /// <summary>
279 /// Tests two objects to determine if they are strings.
280 /// </summary>
281 /// <param name="expected"></param>
282 /// <param name="actual"></param>
283 /// <returns></returns>
284 static protected bool InputsAreStrings( Object expected, Object actual )
286 if( null != expected &&
287 null != actual &&
288 expected is string &&
289 actual is string )
291 return true;
293 return false;
296 /// <summary>
297 /// Tests if two strings are different lengths.
298 /// </summary>
299 /// <param name="sExpected"></param>
300 /// <param name="sActual"></param>
301 /// <returns>True if string lengths are different</returns>
302 static protected bool LengthsDifferent( string sExpected, string sActual )
304 if( sExpected.Length != sActual.Length )
306 return true;
308 return false;
311 /// <summary>
312 /// Tests if two arrays are different lengths.
313 /// </summary>
314 /// <param name="sExpected"></param>
315 /// <param name="sActual"></param>
316 /// <returns>True if array lengths are different</returns>
317 static protected bool LengthsDifferent( object[] expected, object[] actual )
319 if( expected.Length != actual.Length )
321 return true;
323 return false;
326 /// <summary>
327 /// Used to construct a message when the lengths of two strings are
328 /// different. Also includes the strings themselves, to allow them
329 /// to be compared visually.
330 /// </summary>
331 /// <param name="sbOutput"></param>
332 /// <param name="sExpected"></param>
333 /// <param name="sActual"></param>
334 static protected void BuildLengthsDifferentMessage( StringBuilder sbOutput, string sExpected, string sActual )
336 BuildContentDifferentMessage( sbOutput, sExpected, sActual );
339 /// <summary>
340 /// Reports the length of two strings that are different lengths
341 /// </summary>
342 /// <param name="sbOutput"></param>
343 /// <param name="sExpected"></param>
344 /// <param name="sActual"></param>
345 static protected void BuildStringLengthDifferentReport( StringBuilder sbOutput, string sExpected, string sActual )
347 sbOutput.Append( "String lengths differ. Expected length=" );
348 sbOutput.Append( sExpected.Length );
349 sbOutput.Append( ", but was length=" );
350 sbOutput.Append( sActual.Length );
351 sbOutput.Append( "." );
352 sbOutput.Append( NewLine );
355 /// <summary>
356 /// Reports the length of two strings that are the same length
357 /// </summary>
358 /// <param name="sbOutput"></param>
359 /// <param name="sExpected"></param>
360 /// <param name="sActual"></param>
361 static protected void BuildStringLengthSameReport( StringBuilder sbOutput, string sExpected, string sActual )
363 sbOutput.Append( "String lengths are both " );
364 sbOutput.Append( sExpected.Length );
365 sbOutput.Append( "." );
366 sbOutput.Append( NewLine );
369 /// <summary>
370 /// Reports whether the string lengths are the same or different, and
371 /// what the string lengths are.
372 /// </summary>
373 /// <param name="sbOutput"></param>
374 /// <param name="sExpected"></param>
375 /// <param name="sActual"></param>
376 static protected void BuildStringLengthReport( StringBuilder sbOutput, string sExpected, string sActual )
378 if( sExpected.Length != sActual.Length )
380 BuildStringLengthDifferentReport( sbOutput, sExpected, sActual );
382 else
384 BuildStringLengthSameReport( sbOutput, sExpected, sActual );
388 /// <summary>
389 /// Reports the length of two arrays that are different lengths
390 /// </summary>
391 /// <param name="sbOutput"></param>
392 /// <param name="expected"></param>
393 /// <param name="actual"></param>
394 static protected void BuildArrayLengthDifferentReport( StringBuilder sbOutput, Array expected, Array actual )
396 sbOutput.Append( "Array lengths differ. Expected length=" );
397 sbOutput.Append( expected.Length );
398 sbOutput.Append( ", but was length=" );
399 sbOutput.Append( actual.Length );
400 sbOutput.Append( "." );
401 sbOutput.Append( NewLine );
404 /// <summary>
405 /// Reports the length of two arrays that are the same length
406 /// </summary>
407 /// <param name="sbOutput"></param>
408 /// <param name="expected"></param>
409 /// <param name="actual"></param>
410 static protected void BuildArrayLengthSameReport( StringBuilder sbOutput, Array expected, Array actual )
412 sbOutput.Append( "Array lengths are both " );
413 sbOutput.Append( expected.Length );
414 sbOutput.Append( "." );
415 sbOutput.Append( NewLine );
418 /// <summary>
419 /// Reports whether the array lengths are the same or different, and
420 /// what the array lengths are.
421 /// </summary>
422 /// <param name="sbOutput"></param>
423 /// <param name="expected"></param>
424 /// <param name="actual"></param>
425 static protected void BuildArrayLengthReport( StringBuilder sbOutput, Array expected, Array actual )
427 if( expected.Length != actual.Length )
429 BuildArrayLengthDifferentReport( sbOutput, expected, actual );
431 else
433 BuildArrayLengthSameReport( sbOutput, expected, actual );
437 /// <summary>
438 ///
439 /// </summary>
440 /// <param name="sbOutput"></param>
441 /// <param name="sExpected"></param>
442 /// <param name="sActual"></param>
443 /// <param name="iPosition"></param>
444 static private void BuildContentDifferentAtPosition( StringBuilder sbOutput, string sExpected, string sActual, int iPosition )
446 BuildStringLengthReport( sbOutput, sExpected, sActual );
448 sbOutput.Append( "Strings differ at index " );
449 sbOutput.Append( iPosition );
450 sbOutput.Append( "." );
451 sbOutput.Append( NewLine );
454 // Clips the strings, then turns any hidden whitespace into visible
455 // characters
457 string sClippedExpected = ConvertWhitespace(ClipAroundPosition( sExpected, iPosition ));
458 string sClippedActual = ConvertWhitespace(ClipAroundPosition( sActual, iPosition ));
460 AppendExpectedAndActual(
461 sbOutput,
462 sClippedExpected,
463 sClippedActual );
464 sbOutput.Append( NewLine );
466 // Add a line showing where they differ. If the string lengths are
467 // different, they start differing just past the length of the
468 // shorter string
469 AppendPositionMarker(
470 sbOutput,
471 FindMismatchPosition( sClippedExpected, sClippedActual, 0 ) );
472 sbOutput.Append( NewLine );
475 /// <summary>
476 /// Turns CR, LF, or TAB into visual indicator to preserve visual marker
477 /// position. This is done by replacing the '\r' into '\\' and 'r'
478 /// characters, and the '\n' into '\\' and 'n' characters, and '\t' into
479 /// '\\' and 't' characters.
480 ///
481 /// Thus the single character becomes two characters for display.
482 /// </summary>
483 /// <param name="sInput"></param>
484 /// <returns></returns>
485 static protected string ConvertWhitespace( string sInput )
487 if( null != sInput )
489 sInput = sInput.Replace( "\r", "\\r" );
490 sInput = sInput.Replace( "\n", "\\n" );
491 sInput = sInput.Replace( "\t", "\\t" );
493 return sInput;
496 /// <summary>
497 /// Shows the position two strings start to differ. Comparison
498 /// starts at the start index.
499 /// </summary>
500 /// <param name="sExpected"></param>
501 /// <param name="sActual"></param>
502 /// <param name="iStart"></param>
503 /// <returns>-1 if no mismatch found, or the index where mismatch found</returns>
504 static private int FindMismatchPosition( string sExpected, string sActual, int iStart )
506 int iLength = Math.Min( sExpected.Length, sActual.Length );
507 for( int i=iStart; i<iLength; i++ )
510 // If they mismatch at a specified position, report the
511 // difference.
513 if( sExpected[i] != sActual[i] )
515 return i;
519 // Strings have same content up to the length of the shorter string.
520 // Mismatch occurs because string lengths are different, so show
521 // that they start differing where the shortest string ends
523 if( sExpected.Length != sActual.Length )
525 return iLength;
529 // Same strings
531 Assert.IsTrue( sExpected.Equals( sActual ) );
532 return -1;
535 /// <summary>
536 /// Constructs a message that can be displayed when the content of two
537 /// strings are different, but the string lengths are the same. The
538 /// message will clip the strings to a reasonable length, centered
539 /// around the first position where they are mismatched, and draw
540 /// a line marking the position of the difference to make comparison
541 /// quicker.
542 /// </summary>
543 /// <param name="sbOutput"></param>
544 /// <param name="sExpected"></param>
545 /// <param name="sActual"></param>
546 static protected void BuildContentDifferentMessage( StringBuilder sbOutput, string sExpected, string sActual )
549 // If they mismatch at a specified position, report the
550 // difference.
552 int iMismatch = FindMismatchPosition( sExpected, sActual, 0 );
553 if( -1 != iMismatch )
555 BuildContentDifferentAtPosition(
556 sbOutput,
557 sExpected,
558 sActual,
559 iMismatch );
560 return;
564 // If the lengths differ, but they match up to the length,
565 // show the difference just past the length of the shorter
566 // string
568 if( sExpected.Length != sActual.Length )
570 BuildContentDifferentAtPosition(
571 sbOutput,
572 sExpected,
573 sActual,
574 Math.Min(sExpected.Length, sActual.Length) );
578 /// <summary>
579 /// Called to append a message when the input strings are different.
580 /// A different message is rendered when the lengths are mismatched,
581 /// and when the lengths match but content is mismatched.
582 /// </summary>
583 /// <param name="sbOutput"></param>
584 /// <param name="expected"></param>
585 /// <param name="actual"></param>
586 static private void BuildStringsDifferentMessage( StringBuilder sbOutput, string expected, string actual )
588 sbOutput.Append( NewLine );
589 if( LengthsDifferent( expected, actual ) )
591 BuildLengthsDifferentMessage( sbOutput, expected, actual );
593 else
595 BuildContentDifferentMessage( sbOutput, expected, actual );
599 /// <summary>
600 /// Called to append a message when the input arrays are different.
601 /// A different message is rendered when the lengths are mismatched,
602 /// and when the lengths match but content is mismatched.
603 /// </summary>
604 /// <param name="sbOutput"></param>
605 /// <param name="expected"></param>
606 /// <param name="actual"></param>
607 static private void BuildArraysDifferentMessage( StringBuilder sbOutput, int index, Array expected, Array actual )
609 sbOutput.Append( NewLine );
611 BuildArrayLengthReport( sbOutput, expected, actual );
613 sbOutput.Append( "Arrays differ at index " );
614 sbOutput.Append( index );
615 sbOutput.Append( "." );
616 sbOutput.Append( NewLine );
618 if ( index < expected.Length && index < actual.Length )
620 if( InputsAreStrings( expected.GetValue(index), actual.GetValue(index) ) )
622 BuildStringsDifferentMessage(
623 sbOutput,
624 (string)expected.GetValue(index),
625 (string)actual.GetValue(index) );
627 else
629 AppendExpectedAndActual( sbOutput, expected.GetValue(index), actual.GetValue(index) );
632 else if( expected.Length < actual.Length )
634 sbOutput.Append( NewLine );
635 sbOutput.Append( " extra:<" );
636 DisplayElements( sbOutput, actual, index, 3 );
637 sbOutput.Append( ">" );
639 else
641 sbOutput.Append( NewLine );
642 sbOutput.Append( " missing:<" );
643 DisplayElements( sbOutput, expected, index, 3 );
644 sbOutput.Append( ">" );
647 return;
650 static private void DisplayElements( StringBuilder sbOutput, Array array, int index, int max )
652 for( int i = 0; i < max; i++ )
654 sbOutput.Append( DisplayString( array.GetValue(index++) ) );
656 if ( index >= array.Length )
657 return;
659 sbOutput.Append( "," );
662 sbOutput.Append( "..." );
665 /// <summary>
666 /// Used to create a StringBuilder that is used for constructing
667 /// the output message when text is different. Handles initialization
668 /// when a message is provided. If message is null, an empty
669 /// StringBuilder is returned.
670 /// </summary>
671 /// <param name="message"></param>
672 /// <returns></returns>
673 static protected StringBuilder CreateStringBuilder( string message, params object[] args )
675 StringBuilder sbOutput;
676 if (message != null)
678 if ( args != null && args.Length > 0 )
679 sbOutput = new StringBuilder( string.Format( message, args ) );
680 else
681 sbOutput = new StringBuilder( message );
683 else
685 sbOutput = new StringBuilder();
687 return sbOutput;
690 /// <summary>
691 /// Called to create a message when two objects have been found to
692 /// be unequal. If the inputs are strings, a special message is
693 /// rendered that can help track down where the strings are different,
694 /// based on differences in length, or differences in content.
695 ///
696 /// If the inputs are not strings, the ToString method of the objects
697 /// is used to show what is different about them.
698 /// </summary>
699 /// <param name="expected"></param>
700 /// <param name="actual"></param>
701 /// <param name="message"></param>
702 /// <param name="args"></param>
703 /// <returns></returns>
704 static public string FormatMessageForFailNotEquals(Object expected, Object actual,
705 string message, params object[] args)
707 StringBuilder sbOutput = CreateStringBuilder( message, args );
708 if( null != message )
710 if( message.Length > 0 )
712 sbOutput.Append( " " );
716 if( InputsAreStrings( expected, actual ) )
718 BuildStringsDifferentMessage(
719 sbOutput,
720 (string)expected,
721 (string)actual );
723 else
725 AppendExpectedAndActual( sbOutput, expected, actual );
727 return sbOutput.ToString();
730 /// <summary>
731 /// Called to create a message when two arrays are not equal.
732 /// </summary>
733 /// <param name="message"></param>
734 /// <param name="expected"></param>
735 /// <param name="actual"></param>
736 /// <returns></returns>
737 static public string FormatMessageForFailArraysNotEqual(int index, Array expected, Array actual,
738 string message, params object[] args)
740 StringBuilder sbOutput = CreateStringBuilder( message, args );
741 if( null != message )
743 if( message.Length > 0 )
745 sbOutput.Append( " " );
749 BuildArraysDifferentMessage(
750 sbOutput,
751 index,
752 expected,
753 actual );
755 return sbOutput.ToString();