4
namespace Nunit.Framework
7
/// Summary description for AssertionFailureMessage.
9
public class AssertionFailureMessage
11
protected AssertionFailureMessage()
15
/// Number of characters before a highlighted position before
16
/// clipping will occur. Clipped text is replaced with an
19
static protected int PreClipLength
28
/// Number of characters after a highlighted position before
29
/// clipping will occur. Clipped text is replaced with an
32
static protected int PostClipLength
41
/// Called to test if the position will cause clipping
42
/// to occur in the early part of a string.
44
/// <param name="iPosition"></param>
45
/// <returns></returns>
46
static private bool IsPreClipped( int position )
48
if( position > PreClipLength )
56
/// Called to test if the position will cause clipping
57
/// to occur in the later part of a string past the
58
/// specified position.
60
/// <param name="sString"></param>
61
/// <param name="iPosition"></param>
62
/// <returns></returns>
63
static private bool IsPostClipped( string sString, int iPosition )
65
if( sString.Length - iPosition > PostClipLength )
73
/// Property called to insert newline characters into a string
75
static private string NewLine
84
/// Renders up to M characters before, and up to N characters after
85
/// the specified index position. If leading or trailing text is
86
/// clipped, and elipses "..." is added where the missing text would
89
/// Clips strings to limit previous or post newline characters,
90
/// since these mess up the comparison
92
/// <param name="sString"></param>
93
/// <param name="iPosition"></param>
94
/// <returns></returns>
95
static protected string ClipAroundPosition( string sString, int iPosition )
97
if( null == sString || 0 == sString.Length )
102
return BuildBefore( sString, iPosition ) + BuildAfter( sString, iPosition );
106
/// Clips the string before the specified position, and appends
107
/// ellipses (...) to show that clipping has occurred
109
/// <param name="sString"></param>
110
/// <param name="iPosition"></param>
111
/// <returns></returns>
112
static protected string PreClip( string sString, int iPosition )
114
return "..." + sString.Substring( iPosition - PreClipLength, PreClipLength );
118
/// Clips the string after the specified position, and appends
119
/// ellipses (...) to show that clipping has occurred
121
/// <param name="sString"></param>
122
/// <param name="iPosition"></param>
123
/// <returns></returns>
124
static protected string PostClip( string sString, int iPosition )
126
return sString.Substring( iPosition, PostClipLength ) + "...";
130
/// Builds the first half of a string, limiting the number of
131
/// characters before the position, and removing newline
132
/// characters. If the leading string is truncated, the
133
/// ellipses (...) characters are appened.
135
/// <param name="sString"></param>
136
/// <param name="iPosition"></param>
137
/// <returns></returns>
138
static private string BuildBefore( string sString, int iPosition )
140
if( IsPreClipped(iPosition) )
142
return PreClip( sString, iPosition );
144
return sString.Substring( 0, iPosition );
148
/// Builds the last half of a string, limiting the number of
149
/// characters after the position, and removing newline
150
/// characters. If the string is truncated, the
151
/// ellipses (...) characters are appened.
153
/// <param name="sString"></param>
154
/// <param name="iPosition"></param>
155
/// <returns></returns>
156
static private string BuildAfter( string sString, int iPosition )
158
if( IsPostClipped(sString, iPosition) )
160
return PostClip( sString, iPosition );
162
return sString.Substring( iPosition );
166
/// Text that is rendered for the expected value
168
/// <returns></returns>
169
static protected string ExpectedText()
175
/// Text rendered for the actual value. This text should
176
/// be the same length as the Expected text, so leading
177
/// spaces should pad this string to ensure they match.
179
/// <returns></returns>
180
static protected string ButWasText()
186
/// Raw line that communicates the expected value, and the actual value
188
/// <param name="sbOutput"></param>
189
/// <param name="expected"></param>
190
/// <param name="actual"></param>
191
static protected void AppendExpectedAndActual( StringBuilder sbOutput, Object expected, Object actual )
193
sbOutput.Append( NewLine );
194
sbOutput.Append( ExpectedText() );
195
sbOutput.Append( (expected != null) ? expected : "(null)" );
196
sbOutput.Append( ">" );
197
sbOutput.Append( NewLine );
198
sbOutput.Append( ButWasText() );
199
sbOutput.Append( (actual != null) ? actual : "(null)" );
200
sbOutput.Append( ">" );
204
/// Draws a marker under the expected/actual strings that highlights
205
/// where in the string a mismatch occurred.
207
/// <param name="sbOutput"></param>
208
/// <param name="iPosition"></param>
209
static protected void AppendPositionMarker( StringBuilder sbOutput, int iPosition )
211
sbOutput.Append( new String( '-', ButWasText().Length ) );
214
sbOutput.Append( new string( '-', iPosition ) );
216
sbOutput.Append( "^" );
220
/// Tests two objects to determine if they are strings.
222
/// <param name="expected"></param>
223
/// <param name="actual"></param>
224
/// <returns></returns>
225
static protected bool InputsAreStrings( Object expected, Object actual )
227
if( null != expected &&
229
expected is string &&
238
/// Tests if two strings are different lengths.
240
/// <param name="sExpected"></param>
241
/// <param name="sActual"></param>
242
/// <returns>True if string lengths are different</returns>
243
static protected bool LengthsDifferent( string sExpected, string sActual )
245
if( sExpected.Length != sActual.Length )
253
/// Used to construct a message when the lengths of two strings are
254
/// different. Also includes the strings themselves, to allow them
255
/// to be compared visually.
257
/// <param name="sbOutput"></param>
258
/// <param name="sExpected"></param>
259
/// <param name="sActual"></param>
260
static protected void BuildLengthsDifferentMessage( StringBuilder sbOutput, string sExpected, string sActual )
262
BuildContentDifferentMessage( sbOutput, sExpected, sActual );
266
/// Reports the length of two strings that are different lengths
268
/// <param name="sbOutput"></param>
269
/// <param name="sExpected"></param>
270
/// <param name="sActual"></param>
271
static protected void BuildStringLengthDifferentReport( StringBuilder sbOutput, string sExpected, string sActual )
273
sbOutput.Append( "String lengths differ. Expected length=" );
274
sbOutput.Append( sExpected.Length );
275
sbOutput.Append( ", but was length=" );
276
sbOutput.Append( sActual.Length );
277
sbOutput.Append( "." );
278
sbOutput.Append( NewLine );
282
/// Reports the length of two strings that are the same length
284
/// <param name="sbOutput"></param>
285
/// <param name="sExpected"></param>
286
/// <param name="sActual"></param>
287
static protected void BuildStringLengthSameReport( StringBuilder sbOutput, string sExpected, string sActual )
289
sbOutput.Append( "String lengths are both " );
290
sbOutput.Append( sExpected.Length );
291
sbOutput.Append( "." );
292
sbOutput.Append( NewLine );
296
/// Reports whether the string lengths are the same or different, and
297
/// what the string lengths are.
299
/// <param name="sbOutput"></param>
300
/// <param name="sExpected"></param>
301
/// <param name="sActual"></param>
302
static protected void BuildStringLengthReport( StringBuilder sbOutput, string sExpected, string sActual )
304
if( sExpected.Length != sActual.Length )
306
BuildStringLengthDifferentReport( sbOutput, sExpected, sActual );
310
BuildStringLengthSameReport( sbOutput, sExpected, sActual );
317
/// <param name="sbOutput"></param>
318
/// <param name="sExpected"></param>
319
/// <param name="sActual"></param>
320
/// <param name="iPosition"></param>
321
static private void BuildContentDifferentAtPosition( StringBuilder sbOutput, string sExpected, string sActual, int iPosition )
323
BuildStringLengthReport( sbOutput, sExpected, sActual );
325
sbOutput.Append( "Strings differ at index " );
326
sbOutput.Append( iPosition );
327
sbOutput.Append( "." );
328
sbOutput.Append( NewLine );
331
// Clips the strings, then turns any hidden newlines into visible
332
// characters by replacing the '\r' into '\\' and 'r' characters,
333
// and the '\n' into '\\' and 'n' characters. Thus the single
334
// character becomes two characters for display.
336
string sClippedExpected = ConvertNewlines(ClipAroundPosition( sExpected, iPosition ));
337
string sClippedActual = ConvertNewlines(ClipAroundPosition( sActual, iPosition ));
339
AppendExpectedAndActual(
343
sbOutput.Append( NewLine );
345
// Add a line showing where they differ. If the string lengths are
346
// different, they start differing just past the length of the
348
AppendPositionMarker(
350
FindMismatchPosition( sClippedExpected, sClippedActual, 0 ) );
351
sbOutput.Append( NewLine );
355
/// Turns CR or LF into visual indicator to preserve visual marker
358
/// <param name="sInput"></param>
359
/// <returns></returns>
360
static protected string ConvertNewlines( string sInput )
364
sInput = sInput.Replace( "\r", "\\r" );
365
sInput = sInput.Replace( "\n", "\\n" );
371
/// Shows the position two strings start to differ. Comparison
372
/// starts at the start index.
374
/// <param name="sExpected"></param>
375
/// <param name="sActual"></param>
376
/// <param name="iStart"></param>
377
/// <returns>-1 if no mismatch found, or the index where mismatch found</returns>
378
static private int FindMismatchPosition( string sExpected, string sActual, int iStart )
380
int iLength = Math.Min( sExpected.Length, sActual.Length );
381
for( int i=iStart; i<iLength; i++ )
384
// If they mismatch at a specified position, report the
387
if( sExpected[i] != sActual[i] )
393
// Strings have same content up to the length of the shorter string.
394
// Mismatch occurs because string lengths are different, so show
395
// that they start differing where the shortest string ends
397
if( sExpected.Length != sActual.Length )
405
Assertion.Assert( sExpected.Equals( sActual ) );
410
/// Constructs a message that can be displayed when the content of two
411
/// strings are different, but the string lengths are the same. The
412
/// message will clip the strings to a reasonable length, centered
413
/// around the first position where they are mismatched, and draw
414
/// a line marking the position of the difference to make comparison
417
/// <param name="sbOutput"></param>
418
/// <param name="sExpected"></param>
419
/// <param name="sActual"></param>
420
static protected void BuildContentDifferentMessage( StringBuilder sbOutput, string sExpected, string sActual )
423
// If they mismatch at a specified position, report the
426
int iMismatch = FindMismatchPosition( sExpected, sActual, 0 );
427
if( -1 != iMismatch )
429
BuildContentDifferentAtPosition(
438
// If the lengths differ, but they match up to the length,
439
// show the difference just past the length of the shorter
442
if( sExpected.Length != sActual.Length )
444
BuildContentDifferentAtPosition(
448
Math.Min(sExpected.Length, sActual.Length) );
453
/// Called to append a message when the input strings are different.
454
/// A different message is rendered when the lengths are mismatched,
455
/// and when the lengths match but content is mismatched.
457
/// <param name="sbOutput"></param>
458
/// <param name="expected"></param>
459
/// <param name="actual"></param>
460
static private void BuildStringsDifferentMessage( StringBuilder sbOutput, string expected, string actual )
462
sbOutput.Append( NewLine );
463
if( LengthsDifferent( expected, actual ) )
465
BuildLengthsDifferentMessage( sbOutput, expected, actual );
469
BuildContentDifferentMessage( sbOutput, expected, actual );
474
/// Used to create a StringBuilder that is used for constructing
475
/// the output message when text is different. Handles initialization
476
/// when a message is provided. If message is null, an empty
477
/// StringBuilder is returned.
479
/// <param name="message"></param>
480
/// <returns></returns>
481
static protected StringBuilder CreateStringBuilder( string message )
483
StringBuilder sbOutput;
486
sbOutput = new StringBuilder( message );
490
sbOutput = new StringBuilder();
496
/// Called to create a message when two objects have been found to
497
/// be unequal. If the inputs are strings, a special message is
498
/// rendered that can help track down where the strings are different,
499
/// based on differences in length, or differences in content.
501
/// If the inputs are not strings, the ToString method of the objects
502
/// is used to show what is different about them.
504
/// <param name="message"></param>
505
/// <param name="expected"></param>
506
/// <param name="actual"></param>
507
/// <returns></returns>
508
static public string FormatMessageForFailNotEquals(string message, Object expected, Object actual)
510
StringBuilder sbOutput = CreateStringBuilder( message );
511
if( null != message )
513
if( message.Length > 0 )
515
sbOutput.Append( " " );
519
if( InputsAreStrings( expected, actual ) )
521
BuildStringsDifferentMessage(
528
AppendExpectedAndActual( sbOutput, expected, actual );
530
return sbOutput.ToString();