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