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
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 '***********************************************************************************/
34 namespace NUnit.Framework
37 /// Summary description for AssertionFailureMessage.
39 public class AssertionFailureMessage
42 /// Protected constructor, used since this class is only used via
45 protected AssertionFailureMessage()
49 /// Number of characters before a highlighted position before
50 /// clipping will occur. Clipped text is replaced with an
53 static protected int PreClipLength
62 /// Number of characters after a highlighted position before
63 /// clipping will occur. Clipped text is replaced with an
66 static protected int PostClipLength
75 /// Called to test if the position will cause clipping
76 /// to occur in the early part of a string.
78 /// <param name="iPosition"></param>
79 /// <returns></returns>
80 static private bool IsPreClipped( int iPosition )
82 if( iPosition > PreClipLength )
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.
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 )
107 /// Property called to insert newline characters into a string
109 static private string NewLine
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
123 /// Clips strings to limit previous or post newline characters,
124 /// since these mess up the comparison
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 )
136 return BuildBefore( sString, iPosition ) + BuildAfter( sString, iPosition );
140 /// Clips the string before the specified position, and appends
141 /// ellipses (...) to show that clipping has occurred
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 );
152 /// Clips the string after the specified position, and appends
153 /// ellipses (...) to show that clipping has occurred
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 ) + "...";
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.
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 );
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.
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 );
200 /// Text that is rendered for the expected value
202 /// <returns></returns>
203 static protected string ExpectedText()
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.
213 /// <returns></returns>
214 static protected string ButWasText()
220 /// Raw line that communicates the expected value, and the actual value
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( ">" );
238 /// Display an object as a string
240 /// <param name="obj"></param>
241 /// <returns></returns>
242 static protected string DisplayString( object obj )
246 else if ( obj is string )
247 return Quoted( (string)obj );
249 return obj.ToString();
255 /// <param name="text"></param>
256 /// <returns></returns>
257 static protected string Quoted( string text )
259 return string.Format( "\"{0}\"", text );
263 /// Draws a marker under the expected/actual strings that highlights
264 /// where in the string a mismatch occurred.
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 ) );
273 sbOutput.Append( new string( '-', iPosition ) );
275 sbOutput.Append( "^" );
279 /// Tests two objects to determine if they are strings.
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 &&
288 expected is string &&
297 /// Tests if two strings are different lengths.
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 )
312 /// Tests if two arrays are different lengths.
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 )
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.
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 );
340 /// Reports the length of two strings that are different lengths
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 );
356 /// Reports the length of two strings that are the same length
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 );
370 /// Reports whether the string lengths are the same or different, and
371 /// what the string lengths are.
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 );
384 BuildStringLengthSameReport( sbOutput, sExpected, sActual );
389 /// Reports the length of two arrays that are different lengths
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 );
405 /// Reports the length of two arrays that are the same length
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 );
419 /// Reports whether the array lengths are the same or different, and
420 /// what the array lengths are.
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 );
433 BuildArrayLengthSameReport( sbOutput, expected, actual );
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
457 string sClippedExpected = ConvertWhitespace(ClipAroundPosition( sExpected, iPosition ));
458 string sClippedActual = ConvertWhitespace(ClipAroundPosition( sActual, iPosition ));
460 AppendExpectedAndActual(
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
469 AppendPositionMarker(
471 FindMismatchPosition( sClippedExpected, sClippedActual, 0 ) );
472 sbOutput.Append( NewLine );
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.
481 /// Thus the single character becomes two characters for display.
483 /// <param name="sInput"></param>
484 /// <returns></returns>
485 static protected string ConvertWhitespace( string sInput )
489 sInput = sInput.Replace( "\r", "\\r" );
490 sInput = sInput.Replace( "\n", "\\n" );
491 sInput = sInput.Replace( "\t", "\\t" );
497 /// Shows the position two strings start to differ. Comparison
498 /// starts at the start index.
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
513 if( sExpected[i] != sActual[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 )
531 Assert.IsTrue( sExpected.Equals( sActual ) );
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
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
552 int iMismatch = FindMismatchPosition( sExpected, sActual, 0 );
553 if( -1 != iMismatch )
555 BuildContentDifferentAtPosition(
564 // If the lengths differ, but they match up to the length,
565 // show the difference just past the length of the shorter
568 if( sExpected.Length != sActual.Length )
570 BuildContentDifferentAtPosition(
574 Math.Min(sExpected.Length, sActual.Length) );
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.
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 );
595 BuildContentDifferentMessage( sbOutput, expected, actual );
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.
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(
624 (string)expected.GetValue(index),
625 (string)actual.GetValue(index) );
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( ">" );
641 sbOutput.Append( NewLine );
642 sbOutput.Append( " missing:<" );
643 DisplayElements( sbOutput, expected, index, 3 );
644 sbOutput.Append( ">" );
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 )
659 sbOutput.Append( "," );
662 sbOutput.Append( "..." );
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.
671 /// <param name="message"></param>
672 /// <returns></returns>
673 static protected StringBuilder CreateStringBuilder( string message, params object[] args )
675 StringBuilder sbOutput;
678 if ( args != null && args.Length > 0 )
679 sbOutput = new StringBuilder( string.Format( message, args ) );
681 sbOutput = new StringBuilder( message );
685 sbOutput = new StringBuilder();
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.
696 /// If the inputs are not strings, the ToString method of the objects
697 /// is used to show what is different about them.
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(
725 AppendExpectedAndActual( sbOutput, expected, actual );
727 return sbOutput.ToString();
731 /// Called to create a message when two arrays are not equal.
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(
755 return sbOutput.ToString();